代码随想录算法训练营第九天|28. 实现 strStr()、459.重复的子字符串「字符串」

88 阅读3分钟

28. 找出字符串中第一个匹配项的下标

题目
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 。

思路

  1. 对栈字符串进行遍历
    1. 设置一个栈指针hayIndex值为当前遍历的起始值,设置一个目标指针为0
    2. 设置一个循环,条件为是否栈中指针当前所指的元素与目标中指针所指的元素相等
      1. 目标指针++,栈指针++
      2. 判断当前目标指针的值是否为当前目标的长度,如果是,直接返回当前栈中起始值的下标
  2. 返回-1

代码

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    for (let i = 0; i<haystack.length; i++) {
        let needIndex = 0,hayIndex = i
        while (haystack[hayIndex]===needle[needIndex] ) {
            needIndex++
            hayIndex++
            if (needIndex === needle.length) {
                return i
            }
        }
    }
    return -1
};

优化:

看了代码随想录的思路是用KMP算法,基本步骤分为两步:

  1. 先求出next数组,这里面存放着模式串的每个元素上的最长相等前后缀
  2. 利用next数组,对文本串和模式串进行匹配,可以在字符串不匹配的时候,找到匹配过的一部分文本内容。 具体思路:
  3. 获取next数组:
    1. 声明一个next数组,包含一个值0
    2. 声明一个变量j,作为前缀的末尾值
    3. 声明一个循环,循环中存在变量i,作为后缀的末尾值,i<模式串的长度
      1. 声明一个循环,判断当前模式串中前缀末尾指向的值不等于后缀末尾指向的值,并且前缀末尾大于0,就执行给前缀末尾赋值在next数组中它上一个指向的值
      2. 判断如果当前模式串中前缀末尾指向的值等于后缀末尾指向的值,前缀末尾+1
      3. 把前缀末尾指向的值赋值给当前后缀末尾指向的值
    4. 返回next数组
  4. 进行字符串匹配操作:
    1. 先获取next数组
    2. 声明一个文本串的指针和模式串的指针,初始值都为0
    3. 声明一个循环,判断条件为文本串的指针小于文本串的长度
      1. 声明一个循环判断当前文本串中文本串指针指向的值与模式串中模式串指针指向的值是否相等。如果相等:
        1. 分别把两个指针右移一位
        2. 判断如果当前模式串指针等于模式串长度,说明模式串已经匹配完毕,直接返回当前文本串指针减去模式串指针的值。
      2. 判断如果当前文本串中文本串指针指向的值与模式串中模式串指针指向的值是否相等并且模式串指针是否大于0(排除模式串指针为0时的情况)
        1. 如果满足条件就把模式串指针的值设为next数组中,模式串指针位置的前一个值,代表的是已经匹配了的最长的字符串的位置。
        2. 如果不满足就把文本串指针+1,进行下一次循环的比较。
    4. 以上比较都没有返回值,说明文本串中没有模式串匹配的值,直接返回-1

代码

/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function(haystack, needle) {
    const next = getNext(needle)
    console.log(next)
    let needIdx = 0,hayIdx = 0
    while (hayIdx<haystack.length) {
        while (haystack[hayIdx]===needle[needIdx]) {
            hayIdx++
            needIdx++
            if (needIdx===needle.length) {
                return hayIdx-needIdx
            }
        }
        if (needIdx>0&&haystack[hayIdx]!==needle[needIdx]) {
            needIdx = next[needIdx-1]
        }else hayIdx++
    }
    return -1
};

const getNext = (s) => {
    const next = [0]
    let j = 0
    for (let i = j+1;i<s.length;i++) {
        while (j>0&&s[i]!==s[j]) {
            j = next[j-1]
        }
        if (s[i]===s[j]) {
            j++
        }
        next[i] = j
    }
    return next
}

总结:

第一次接触KMP算法,原理上对最长相等前后缀的问题有点半懂,后来看了代码实现,对获取next数组的整个方法也都是半懂,但这时候我自己拿个例子去推一遍整个流程,是能完全推出来的,推完以后感觉更理解了一点,然后自己尝试着写了一遍这道题,虽然中间有卡壳,但是在多次debug以后也能写出来,感觉收获很多。

重复的子字符串

题目
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

思路
没有思路。

代码
写不出来。

优化:

看了代码随想录的思路,是用了移动匹配和KMP算法两种方法,移动匹配方法的思路已经足够让我惊叹了,对于KMP算法的处理让我叹为观止,就觉得不知道怎么想到的,原本以为拿到next数组以后还要再比较一下还是怎么样,但是其实就是对索引值进行了一个判断,就感觉很难想到,下面给出我再看过思路以后写出来的答案。

代码

// 移动匹配
const repeatedSubstringPattern = s => (s+s).slice(1,(s+s).length-1).includes(s)

// KMP算法
/**
 * @param {string} s
 * @return {boolean}
 */
var repeatedSubstringPattern = function(s) {
    const next = getNext(s)
    console.log(next)
    const index = s.length - next[next.length-1]
    return next[next.length-1]!==0&&s.length%index===0?true:false
};

const getNext = (s) => {
    const next = [0]
    let j = 0
    for (let i = j+1;i<s.length;i++) {
        while (j>0&&s[i]!==s[j]) {
            j = next[j-1]
        }
        if (s[i]===s[j]) {
            j++
        }
        next[i] = j
    }
    return next
}

总结:

这道题其实用暴力解法可以做出来,然后自己就直接没考虑,然后一直在想KMP算法怎么做,甚至想直接对next数组进行判断处理,后来发现自己陷入思维定式了,看了思路以后还是能做出来。

Day9总结

今天的题说难,用KMP算法确实挺难的,自己很难想出来,况且没接触过,但是今天的题目建议跳过,自己还是看了一遍,感觉KMP对自己来说还是能理解的,但是还是要多重复几遍,感觉自己的理解还是比较浅的。 最近可能是放假了,这两天在忙毕业,拍毕业照拿毕业证书,不知道为什么感觉自己很浮躁,有时候做着做着就静不下心来了,下次开始给自己限定时间吧。