The second issue of [Leetcode algorithm weekly]

Keywords: Javascript ascii Java C

First appeared in the WeChat public's front-end growth notes, written in 2019.11.05

background

This paper records the whole thinking process in the process of writing questions for reference. The main contents include:

  • Assumption of topic analysis
  • Write code validation
  • Consult others' solution
  • Thinking and summing up

Catalog

Easy

20. Valid brackets

Title address

Title Description

Given a string that only includes' (',' '', '{', '}', '[', ']', determine whether the string is valid.

Valid string needs to meet:

  1. The opening bracket must be closed with a closing bracket of the same type.
  2. The left parenthesis must be closed in the correct order.

Note that empty strings can be considered valid strings.

Example:

Input: ()
Output: true

Input: '() [] {}'
Output: true

Input: "(").
Output: false

Input: '([)]'
Output: false

Type: '{[]}'
Output: true

Assumption of topic analysis

From the point of view of the problem, it is still necessary to traverse the string, find the matching brackets, and then continue to do the processing. So my idea of solving this problem is:

  • Use the stack to record. The matched pair will go out of the stack. Finally, judge whether the stack is empty

There are several points to be noted, which can reduce the amount of calculation:

  1. The question surface shows that the string only contains three brackets, so the length is odd, which must be invalid
  2. As long as there is a pair of inconformity, it can be determined to be invalid
  3. Stack length is more than half of string length, it must be invalid
  4. It must be invalid to find the right bracket first

Write code validation

I. record stack

Code:

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    if (s === '') return true;
    if (s.length % 2) return false;
    // Index the hash table
    const hash = {
        '(': ')',
        '[': ']',
        '{': '}'
    }
    let arr = []
    for (let i = 0; i < s.length; i++) {
        if (!hash[s.charAt(i)]) { // Right parenthesis pushed in
            if (!arr.length || hash[arr[arr.length - 1]] !== s.charAt(i)) {
                return false
            } else {
                arr.pop()
            }
        } else {
            if (arr.length >= s / 2) {   // More than half the length
                return false
            }
            arr.push(s.charAt(i))
        }
    }
    return !arr.length
};

Result:

  • 76/76 cases passed (64 ms)
  • Your runtime beats 90.67 % of javascript submissions
  • Your memory usage beats 64.59 % of javascript submissions (33.8 MB)
  • Time complexity: O(n)

Consult others' solution

Find a very violent solution, although the efficiency is not high, but the thinking is strange. Let's look at the implementation:

I. violent regularity

Code:

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    if (s === '') return true;
    if (s.length % 2) return false;

    while(s.length) {
        const s_ = s
        s = s.replace('()','').replace('[]','').replace('{}','')
        if (s === s_) return false;
    }
    return true;
};

Result:

  • 76/76 cases passed (104 ms)
  • Your runtime beats 14.95 % of javascript submissions
  • Your memory usage beats 19.75 % of javascript submissions (35.7 MB)
  • Time complexity: O(n)

Thinking and summing up

As far as this problem is concerned, I prefer to add an auxiliary stack for recording. Because once you remove the parenthesis only constraint, the regular will not be able to solve it.

21. Merge two ordered lists

Title address

Title Description

Merge two ordered lists into a new ordered list and return. The new linked list is composed of all nodes of two given linked lists.

Example:

Input: 1 - > 2 - > 4, 1 - > 3 - > 4
 Output: 1 - > 1 - > 2 - > 3 - > 4 - > 4

Assumption of topic analysis

This problem shows that it is a related problem of linked list. To merge linked list, it is no more than to modify the pointer of linked list or join linked list. Therefore, I have two solutions to this problem:

  • Modify the pointer, take out the number in one linked list and insert it into another linked list
  • Link list splicing: recursively compare which link list has smaller elements, and then cut and splice it to another one

The difference between the two methods is obvious. The way to modify the pointer needs to store and constantly modify the pointer. The way to splice is to splice the linked list directly.

Of course, there are some special values to consider here.

Write code validation

I. modify pointer

Code:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    if (l1 === null) return l2
    if (l2 === null) return l1
    // Result list
    let l = new ListNode(0)
    // The current node pointer is updated continuously, and the object is assigned as the address, so you can change the pointer to
    let cursor = l
    // One will traverse first and become null
    while(l1 !== null && l2 !== null) {
        if (l1.val <= l2.val) { // If it's small, the pointer will point to it
            cursor.next = l1
            l1 = l1.next
        } else {
            cursor.next = l2
            l2 = l2.next
        }
        // It can be understood as l.next.next.next
        cursor = cursor.next
    }
    // If one is empty, it can be spliced directly
    cursor.next = l1 === null ? l2 : l1
    return l.next
};

Result:

  • 208/208 cases passed (60 ms)
  • Your runtime beats 99.51 % of javascript submissions
  • Your memory usage beats 51.04 % of javascript submissions (35.4 MB)
  • Time complexity O(m + n), respectively representing the length of two linked lists

II. Link list splicing

Code:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    if (l1 === null) return l2
    if (l2 === null) return l1
    if (l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2)
        return l1   // This is a combination
    } else {
        l2.next = mergeTwoLists(l1, l2.next)
        return l2   // This is a combination
    }
};

Result:

  • 208/208 cases passed (68 ms)
  • Your runtime beats 96.41 % of javascript submissions
  • Your memory usage beats 51.04 % of javascript submissions (35.4 MB)
  • Time complexity O(m + n), respectively representing the length of two linked lists

Consult others' solution

There are basically two ways of thinking, and no solution with different directions has been found.

It's just that some solutions open up new lists to record, or some differences in details.

Thinking and summing up

Do you find the solution of link list splicing here Previous issue Is the idea of divide and conquer the same in question 14? Yes, in fact, this is also an application of divide and rule.

26. Delete the duplicates in the sorting array

Title address

Title Description

Given a sort array, you need to delete duplicate elements in place so that each element appears only once, returning the new length of the array after removal.

Do not use extra array space, you must modify the input array in place and do so with O(1) extra space.

Example:

Given array nums = [1,1,2],

The function should return a new length of 2, and the first two elements of the original array nums are modified to 1, 2.

You don't need to think about the elements in the array that follow the new length.

Given nums = [0,0,1,1,1,2,2,3,3,4],

The function should return a new length of 5, and the first five elements of the original array nums are modified to 0, 1, 2, 3, 4.

You don't need to think about the elements in the array that follow the new length.

Explain:

Why is the return value an integer, but the output answer is an array?

Note that the input array is passed by reference, which means that modifying the input array in a function is visible to the caller.

You can imagine the internal operation as follows:

// nums is passed by reference. That is, do not make any copies of the arguments
int len = removeDuplicates(nums);

// Modifying the input array in a function is visible to the caller.
// Depending on the length returned by your function, it prints out all elements in the array within that length range.
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

Assumption of topic analysis

If it is a simple array de duplication, there are many ways to do it. Therefore, restrictions have been added to the title. Here are some important points:

  • Do not use extra array space. The space complexity is O(1)
  • Delete duplicate elements in place
  • You do not need to consider elements beyond the new length

This means that it is not allowed to use the new array to solve the problem, that is, to operate on the original array. The last point of attention can be seen that copying array items is a direction, and the second point can be seen that array deletion is a direction. You can't delete more than one element, so you don't need to consider a combination of the two. So I can solve this problem in two directions:

  • Copy array elements
  • Delete array element

Write code validation

I. copy array elements

Code:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
    if (nums.length === 0) return 0;
    var len = 1
    for(let i = 1; i < nums.length; i++) {
        if(nums[i] !== nums[i - 1]) { // The latter is not equal to the former
            nums[len++] = nums[i] // Copy array elements
        }
    }
    return len
};

Result:

  • 161/161 cases passed (68 ms)
  • Your runtime beats 99.81 % of javascript submissions
  • Your memory usage beats 77.54 % of javascript submissions (36.6 MB)
  • Time complexity O(n)

II. Deleting array elements

Code:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
    if (nums.length === 0) return 0;
    for(let i = 1; i < nums.length;) {
        if(nums[i] === nums[i - 1]) { // The latter is equal to the former
            nums.splice(i, 1)
        } else {
            i++
        }
    }
    return nums.length
};

Result:

  • 161/161 cases passed (96 ms)
  • Your runtime beats 75.93 % of javascript submissions
  • Your memory usage beats 30.85 % of javascript submissions (37.3 MB)
  • Time complexity O(n)

Consult others' solution

Here we see a very clever solution, the double finger method. Equivalent to one for counting and one for scanning.

I. double finger needling

Code:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
    if (nums.length === 0) return 0;

    let i = 0;
    for(let j = 1; j < nums.length; j++) {
        if (nums[j] !== nums[i]) {
            nums[++i] = nums[j]
        }
    }
    return i + 1  // Subscript + 1 is array length
};

Result:

  • 161/161 cases passed (68 ms)
  • Your runtime beats 99.81 % of javascript submissions
  • Your memory usage beats 84.03 % of javascript submissions (36.5 MB)
  • Time complexity O(n)

Thinking and summing up

In terms of the three solutions, deleting array elements will frequently modify the array, which is not recommended. The code logic of double finger method is similar to that of copying array elements, but the thinking is quite different.

27. Remove elements

Title address

Title Description

Given an array of nums and a value of val, you need to remove all elements whose values are equal to val in place and return the new length of the array after removal.

Do not use extra array space, you must modify the input array in place and do so with O(1) extra space.

The order of elements can be changed. You don't need to think about the elements in the array that follow the new length.

Example:

Given nums = [3,2,2,3], val = 3,

The function should return a new length of 2, and the first two elements in nums are both 2.

You don't need to think about the elements in the array that follow the new length.

Given nums = [0,1,2,2,3,0,4,2], val = 2,

The function should return a new length of 5, and the first five elements in nums are 0, 1, 3, 0, 4.

Note that the five elements can be in any order.

You don't need to think about the elements in the array that follow the new length.

Explain:

Why is the return value an integer, but the output answer is an array?

Note that the input array is passed by reference, which means that modifying the input array in a function is visible to the caller.

You can imagine the internal operation as follows:

// nums is passed by reference. That is, do not make any copies of the arguments
int len = removeElement(nums, val);

// Modifying the input array in a function is visible to the caller.
// Depending on the length returned by your function, it prints out all elements in the array within that length range.
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

Assumption of topic analysis

This problem is very similar to the last one, so we can follow the direction of the previous problem to solve this problem:

  • Delete array element
  • Double finger acupuncture

Write code validation

I. deleting array elements

Code:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    if (nums.length === 0) return 0;

    for(let i = 0; i < nums.length;) {
        if (nums[i] === val) {
            nums.splice(i, 1)
        } else {
            i++
        }
    }
};

Result:

  • 113/113 cases passed (64 ms)
  • Your runtime beats 89.43 % of javascript submissions
  • Your memory usage beats 47.42 % of javascript submissions (33.7 MB)
  • Time complexity O(n)

II. Double finger needling

Code:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    if (nums.length === 0) return 0;

    let i = 0
    for(let j = 0; j < nums.length; j++) {
        if (nums[j] !== val) {
            nums[i++] = nums[j]
        }
    }
    return i
};

Result:

  • 113/113 cases passed (60 ms)
  • Your runtime beats 95.11 % of javascript submissions
  • Your memory usage beats 98.18 % of javascript submissions (33.3 MB)
  • Time complexity O(n)

Consult others' solution

See two slightly different approaches:

  • Single pointer method, use const of to replace once traversal, but the writing method is different, there is no essential improvement
  • The exchange is removed. At the same time, the last item is exchanged, and the array length is reduced by 1

I. single pointer method

Code:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    if (nums.length === 0) return 0;

    let i = 0;
    for(const num of nums) {
        if(num !== val) {
            nums[i++] = num;
        }
    }
    return i;
};

Result:

  • 113/113 cases passed (68 ms)
  • Your runtime beats 80.29 % of javascript submissions
  • Your memory usage beats 43.35 % of javascript submissions (33.7 MB)
  • Time complexity O(n)

II. Exchange removal

Code:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    if (nums.length === 0) return 0;

    let i = nums.length;
    for(let j = 0; j < i;) {
        if (nums[j] === val) {
            nums[j] = nums[--i]
        } else {
            j++
        }
    }

    return i;
};

Result:

  • 113/113 cases passed (68 ms)
  • Your runtime beats 80.29 % of javascript submissions
  • Your memory usage beats 44.53 % of javascript submissions (33.7 MB)
  • Time complexity O(n)

Thinking and summing up

Here's a new idea: if you want to remove multiple items, it's appropriate to use the pointer method; if you want to remove single items, you need to use the interactive move division method to minimize the number of iterations.

28. Implementation of strStr

Title address

Title Description

Implement the strStr() function.

Given a haystack string and a needle string, find the first place (starting from 0) where the needle string appears in the haystack string. If not, returns - 1.

Example:

Input: haystack = "hello", needle = "ll"
Output: 2

Input: haystack = "aaaaa", needle = "bba"
Output: -1

Explain:

When need is an empty string, what value should we return? This is a good question in an interview.

For this problem, we should return 0 when need is an empty string. This matches the C language's strstr() and Java's indexOf() definitions.

Assumption of topic analysis

This problem is obviously a string search problem. It is estimated that the algorithm is being investigated, but the knowledge is limited. So I will first implement the answer in the existing way, and then learn the algorithm.

  • IndexOf is a native method, so it's meaningless to examine it, so we won't elaborate on it
  • Traversal matching, equivalent to implementing an IndexOf

Write code validation

I. ergodic matching

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1
    for(let i = 0; i < haystack.length; i++) {
        if (i + needle.length > haystack.length) {
            return -1
        } else {
            const str = haystack.substr(i, needle.length)
            if (str === needle) {
                return i
            }
        }
    }
    return -1
};

Result:

  • 74/74 cases passed (64 ms)
  • Your runtime beats 90.58 % of javascript submissions
  • Your memory usage beats 44.22 % of javascript submissions (33.9 MB)
  • Time complexity O(n)

Consult others' solution

First, refer to the introduction to algorithms, and see that there are four types of string matching:

  • Simple string matching algorithm
  • Rabin Karp algorithm
  • String matching using finite automata
  • KMP algorithm

Then look at the solution of the problem and find out the following three algorithms:

  • BM algorithm
  • Horsepool algorithm
  • Sunday algorithm

I. simple string matching algorithm

Algorithm description:

Through a cycle to find all the effective cheap, the cycle of n-m+1 possible s value is tested to see if the condition P[1..m] = T[s+1...s+m] can be met. Where n is the string length and 'm' is the matching string length.

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    let i = 0;
    let j = 0;
    while(j < needle.length && i < haystack.length) {
        if(haystack[i] === needle[j]) { // If the same bit is equal, continue to judge the next bit
            i++;
            j++;
        } else {
            i = i - j + 1; // i migration
            j = 0; // j reset

            if (i + needle.length > haystack.length) { // I add optimization points and reduce some operations
                return -1
            }
        }
    }
    if (j >= needle.length) { // When the substring ratio is finished, j should be equal to needle.length
        return i - needle.length;
    } else {
        return -1
    }
};

Result:

  • 74/74 cases passed (56 ms)
  • Your runtime beats 98.45 % of javascript submissions
  • Your memory usage beats 30.12 % of javascript submissions (34.8 MB)
  • Time complexity O(m*n)

II. Rabin Karp algorithm

Algorithm description:

Carry out hash operation, convert the string to the corresponding hash value for comparison, similar to hexadecimal. The title here is string. I will use ASCII value to represent the hash value of each character. Then we can calculate the hash value of the pattern string and make rolling comparison.

Each time you scroll, you only need to do three fixed operations - * + to get the hash value of the scroll string.

For example, when calculating B B C, the hash value is hash = (b.charCodeAt() * 128 ^ 2 + b.charCodeAt() * 128 + c.charCodeAt()); if the new value bca is to be calculated, it is (hash - b.charCodeAt() * 128 ^ 2) * 128 + c.charCodeAt().

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    let searchHash = 0 // hash value of search string
    let startHash = 0 // hash value at the beginning of string

    for(let i = 0; i < needle.length; i++) {
        searchHash += needle.charCodeAt(i) * Math.pow(2, needle.length - i - 1)
        startHash += haystack.charCodeAt(i) * Math.pow(2, needle.length - i - 1)
    }

    if (startHash === searchHash)  return 0

    for(let j = 1; j < haystack.length - needle.length + 1; j++) {
        startHash = (startHash - haystack.charCodeAt(j - 1) * Math.pow(2, needle.length - 1)) * 2 + haystack.charCodeAt(j + needle.length - 1)
        if (startHash === searchHash) {
            return j
        }
    }
    return -1
};

Result:

  • 74/74 cases passed (68 ms)
  • Your runtime beats 81.31 % of javascript submissions
  • Your memory usage beats 16.86 % of javascript submissions (35.4 MB)
  • Time complexity O(m*n)

Note: there may be spillovers here, so not all of them apply.

III. string matching using finite automata

Algorithm description:

By scanning the text string T, all the positions of pattern P are found. They check each text character only once, and the time taken to check each text character is constant. In a word: character input causes state machine state change, and the expected result is obtained through state transition diagram.

The main core point here is to judge each input and find the longest suffix match. If the longest length is equal to the length of the search string, it must contain the search string.

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    // Find maximum matching suffix length
    function findSuffix (Pq) {
        let suffixLen = 0
        let k = 0
        while(k < Pq.length && k < needle.length) {
            let i = 0;
            for(i = 0; i <= k; i++) {
                // Find how many items in the need are matches of the string corresponding to the current status
                if (Pq.charAt(Pq.length - 1 - k + i) !== needle.charAt(i)) {
                    break;
                }
            }

            // All items match, i.e. suffix found
            if (i - 1 == k) {
                suffixLen = k+1;
             }
            k++
        }
        return suffixLen
    }

    // Get all input character sets, for example, the combination of 'a B C' and 'C D' is ['a','b','c','d ']
    const setArr = Array.from(new Set(haystack + needle)) // User input options

    // Build state machine
    const hash = {}
    for(let q = 0; q < haystack.length; q++) {
        for(let k = 0; k < setArr.length; k++) {
            const char = haystack.substring(0, q) + setArr[k] // Characters for next state
            const nextState = findSuffix(char)
            // Find the value of, for example, 0.a 0.b 0.c
            if (!hash[q]) {
                hash[q] = {}
            }
            hash[q][char] = nextState
        }
    }

    // Solving according to state machine
    let matchStr = ''
    for(let n = 0; n < haystack.length; n++) {
        const map = hash[n]
        matchStr += haystack[n]
        const nextState = map[matchStr]

        if (nextState === needle.length) {
            return n - nextState + 1
        }
    }
    return -1
};

Result:

  • 74/74 cases passed (84 ms)
  • Your runtime beats 35.05 % of javascript submissions
  • Your memory usage beats 5.05 % of javascript submissions (39.8 MB)
  • Time complexity O(n)

Ⅳ. KMP algorithm

Algorithm description:

It can be understood that based on the state machine, a prefix function is used to judge the state. In essence, it is also the idea of prefix and suffix.

Code:

// @lc code=start
/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    // Generate the hash table of the longest common prefix length under each position of the matching string
    function getHash () {
        let i = 0 // arr[i] represents the longest common prefix length of the string before I
        let j = 1
        let hash = {
            0: 0
        }
        while (j < needle.length) {
            if (needle.charAt(i) === needle.charAt(j)) { // Equal direct i j all backward
                hash[j++] = ++i
            } else if (i === 0) {   // If i is the starting point and the two are not equal, then it must be 0
                hash[j] = 0
                j++
            } else {
                // Here's an explanation: because the string before i has the same longest common prefix as the string before j, that is to say, the longest common suffix of the string before i is the same as the longest common prefix of the string before j, i just need to go back to the last bit of the longest common prefix of the string before i to start the comparison
                i = hash[i - 1]
            }
        }
        return hash
    }

    const hash = getHash()
    let i = 0 // Position in parent string
    let j = 0 // Position in substring
    while(i < haystack.length && j < needle.length) {
        if (haystack.charAt(i) === needle.charAt(j)) {  // Two matches, move backward at the same time
            i++
            j++
        } else if (j === 0) { // If the two do not match and j is at the starting point, the parent string moves backward
            i++
        } else {
            j = hash[j - 1]
        }
    }
    if (j === needle.length) {  // The loop is over, indicating that it is matched
        return i - j
    } else {
        return -1
    }
};

Result:

  • 74/74 cases passed (60 ms)
  • Your runtime beats 94.74 % of javascript submissions
  • Your memory usage beats 23.73 % of javascript submissions (35.1 MB)
  • Time complexity O(n)

Ⅴ. BM algorithm

Algorithm description:

Based on suffix matching, matching starts from the back, but moving starts from the front. Only two rules are defined: bad character rule and good suffix rule.

Generally speaking, it is to verify whether it is a bad character first, and then judge whether the corresponding offset in the search term is verified in the next step. If it matches, check from the back to the front. If it still matches, it is a good suffix. The core idea is that each shift takes a larger value in bad character and good suffix rules. Since both rules are only related to matching items, the rule table can be generated in advance.

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    function makeBadChar (needle) {
        let hash = {}
        for(let i = 0; i < 256; i++) { // ascii character length
            hash[String.fromCharCode(i)] = -1 // Initialize to - 1
        }
        for(let i = 0; i < needle.length; i++) {
            hash[needle.charAt(i)] = i  // The position of the last occurrence of the character
        }
        return hash
    }

    function makeGoodSuffix (needle) {
        let hashSuffix = {}
        let hashPrefix = {}
        for(let i = 0; i < needle.length; i++) {
            hashSuffix[i] = -1
            hashPrefix[i] = false
        }
        for(let i = 0; i < needle.length - 1; i++) { // needle[0, i]
            let j = i
            k = 0 // The length of the common suffix substring, with the tail taking k for comparison
            while(j >= 0 && needle.charAt(j) === needle.charAt(needle.length - 1 - k)) { // needle[0,needle.length - 1]
                --j
                ++k
                hashSuffix[k] = j + 1 // Start subscript
            }

            if (j === -1) { // Note that all matches mean that the common suffix substring is also the prefix substring of the pattern at this time
                hashPrefix[k] = true
            }
        }
        return { hashSuffix, hashPrefix }
    }

    function moveGoodSuffix (j, needle) {
        let k = needle.length - 1 - j
        let suffixes = makeGoodSuffix(needle).hashSuffix
        let prefixes = makeGoodSuffix(needle).hashPrefix
        if (suffixes[k] !== -1) { // Found the same substring as a good suffix, get the subscript
            return j - suffixes[k] + 1
        }
        for(let r = j + 2; r < needle.length; ++r) {
            if (prefixes[needle.length - r]) { // Need. Length is a good suffix substring length
                return r // Align prefix to good suffix
            }
        }
        return needle.length // Match all, move string length directly
    }

    let badchar = makeBadChar(needle)
    let i = 0;
    while(i < haystack.length - needle.length + 1) {
        let j
        for(j = needle.length - 1; j >= 0; --j) {
            if (haystack.charAt(i + j) != needle[j]) {
                break; // Bad character, subscript j
            }
        }
        if (j < 0) { // Matching success
            return i // Position of the first matching character
        }
        let moveLen1 = j - badchar[haystack.charAt(i + j)]
        let moveLen2 = 0
        if (j < needle.length -1) { // If there is a good suffix
            moveLen2 = moveGoodSuffix(j, needle)
        }
        i = i + Math.max(moveLen1, moveLen2)
    }

    return -1
};

Result:

  • 74/74 cases passed (72 ms)
  • Your runtime beats 69.29 % of javascript submissions
  • Your memory usage beats 5.05 % of javascript submissions (37 MB)
  • Time complexity O(n)

Ⅵ. Horsepool algorithm

Algorithm description:

Compare the last character of the matching window in the main string with the last character in the pattern string. If equal, continue to compare the main string and pattern string from the back to the front until they are completely equal or do not match at a certain character. If there is no match, the window will move to the right according to the last character in the main string matching window at the next occurrence position in the pattern string.

Code:

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    if (needle === '') return 0
    if (needle.length > haystack.length) return -1
    if (needle.length === haystack.length && needle !== haystack) return -1

    let hash = {}
    for(let i = 0; i < 256; i++) {
        hash[i] = needle.length // The default initialization is the maximum offset, which is the matching string length
    }
    for(let i = 0; i < needle.length - 1; i++) {
        hash[needle.charCodeAt(i)] = needle.length - 1 - i // Distance to the right of each character
    }

    let pos = 0

    while(pos < (haystack.length - needle.length + 1)) {
        let j = needle.length - 1 // From right to left
        while(j >= 0 && haystack.charAt(pos + j) === needle.charAt(j)) {
            j--
        }
        if (j < 0) { // Match all
            return pos
        } else { // Mismatch
            pos += hash[haystack.charCodeAt(pos + needle.length - 1)]
        }
    }

    return -1
};

Result:

  • 74/74 cases passed (68 ms)
  • Your runtime beats 79.76 % of javascript submissions
  • Your memory usage beats 16.14 % of javascript submissions (35.4 MB)
  • Time complexity O(n)

Ⅶ. Sunday algorithm

Algorithm description:

Its idea is similar to BM algorithm, but it matches from front to back. When matching fails, it pays attention to the next character in the main string. If the character does not exist in the matching character, offset one more bit; if it exists, offset the length of the matching string minus the rightmost position of the character.

Code:

Result:

  • 74/74 cases passed (56 ms)
  • Your runtime beats 98.3 % of javascript submissions
  • Your memory usage beats 74.1 % of javascript submissions (33.6 MB)
  • Time complexity O(n)

Thinking and summing up

In terms of the difficulty of understanding, I suggest to look at Sunday algorithm and Horspool algorithm first, but the matching idea of RMP algorithm opens the eyes and uses suffix prefix to deal with the problem. The common string algorithms are all tried here, and the overall results are quite fruitful.

(end)

This article is an original article, which may update knowledge points and correct errors. Therefore, please keep the original source for reprint to facilitate traceability, avoid misleading of old wrong knowledge, and have a better reading experience
If you have any help, welcome to star or fork
(for reprint, please indicate the source: https://chenjiahao.xyz)

Posted by 11Tami on Tue, 05 Nov 2019 08:04:59 -0800