28 - 实现 strStr() - implement-strstr

# 28 - 实现 strStr() - implement-strstr

[TOC]

# 一、题目

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1

示例 1:

输入: haystack = "hello", needle = "ll"
输出: 2
1
2

示例 2:

输入: haystack = "aaaaa", needle = "bba"
输出: -1
1
2

说明:

needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() (opens new window) 以及 Java的 indexOf() (opens new window) 定义相符。

# 二、解法

# 2.1 暴力解法

var strStr = function (haystack, needle) {
    // 1.特况
    if (!needle) return 0
    // 2.一般
    let len = haystack.length
    let nLen = needle.length
    // 一个指针指向needle,一个指针指向haystack
    let i = 0
    for (let j = 0; j < len; j++) {
        if (haystack[j] === needle[i]) {
            i++
            if (i === nLen) return j - nLen + 1
        } else {
            j = j - i
            i = 0
        }
    }
    return -1
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2.2 KMP算法

image-20200123112711442

  • 部分匹配值 = “前缀” 和 “后缀" 的最长的共有元素的长度。
  • 移动位数 = 已匹配的字符数-对应的部分匹配值。
  • 无须回退,效率高。典型的牺牲空间换取时间的算法。

# 2.2.1 部分匹配算法

var kmpGetPartMatchLen = function (str) {
    let partMatch = []
    for (let i = 0; i < str.length; i++) {
        let prefix = '',
            suffix = ''
        let newStr = str.slice(0, i + 1)
        // 判断前后缀是否相同
        for (let j = 0; j < i; j++) { // i 即子串长度
            prefix = newStr.slice(0, j + 1)
            suffix = newStr.slice(-j - 1) // [倒数第0个,倒数第j+1个)
            if (prefix === suffix) {
                partMatch[i] = prefix.length
            }
        }
        // 最终都不存在匹配
        partMatch[i] = partMatch[i] || 0
    }
    return partMatch
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2.2.2 移动位数算法

  • 以haystack为参照,needle向前移动。
let moveLen = matchedLen - partMatch[matchedLen]
1

# 2.2.3 算法实现

  • 也许还能优化,以后再看。
var strStr = function (haystack, needle) {
    if (!needle) return 0
    if (haystack === needle) return 0
    if (needle.length >= haystack.length) return -1
    let partMatch = kmpGetPartMatchLen(needle)
    partMatch[-1] = 0
    let len = haystack.length
    let nLen = needle.length
    let matchedLen = 0 // 匹配的长度
    for (let i = 0; i < len; i++) {
        if (haystack[i] === needle[matchedLen]) {
            matchedLen++
            if (matchedLen === nLen) return i - nLen + 1
        } else {
            if (matchedLen) i--;
            matchedLen = partMatch[matchedLen - 1]
        }
    }
    return -1
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20