小哆啦解题记:字符串里的 “捉迷藏”

111 阅读4分钟

小哆啦解题记:字符串里的 “捉迷藏”

小哆啦开始刷力扣的第十九天

leetcode.cn/problems/fi…

难题降临

在编程小镇里,小哆啦可是个热爱挑战的编程小能手,整天都在代码的世界里探险。有一天,编程精灵小智急匆匆地找到了小哆啦,气喘吁吁地说:“小哆啦,大事不好啦!编程魔法世界出难题啦,得靠你去解开。”

小哆啦一听,眼睛瞬间亮闪闪的,兴奋地搓搓手:“啥难题呀,快说来听听,我小哆啦出马,肯定没问题!”

小智清了清嗓子,说道:“现在有两个字符串 haystack 和 needle,要在 haystack 里找到 needle 第一次出现的位置,要是找不到,就返回 -1。这就好像 needle 在 haystack 里玩捉迷藏,你得把它第一次藏的位置找出来哟。”

初次尝试,遭遇挫折

小哆啦拍着胸脯保证:“这有啥难的,看我的!” 说完,就噼里啪啦地在键盘上敲起了代码:

function strStr(haystack: string, needle: string): number {
    let hayArr = haystack.split();
    let neeArr = needle.split();
    for (let i = 0; i < hayArr.length; i++) {
        let num = i;
        let temp = 0
        while(hayArr[num] == neeArr[temp]){
            num ++ 
            temp ++
            if(temp == neeArr.length -1 ){
                return num
            }
        }
    }
    return -1
};

小哆啦满心欢喜地运行代码,结果却像被泼了一盆冷水,输出全是错的。小哆啦挠着脑袋,急得像热锅上的蚂蚁,嘴里嘟囔着:“咋回事呀,不应该呀。”

错误排查,逐步修正

这时,小智在旁边笑着提醒:“小哆啦,你看哈,split 方法要是不传入参数,它就会把整个字符串当成一个整体放到数组里,根本不会按字符分割哟。就好像你要把一堆糖果一颗一颗分开,结果没说清楚,糖果还是整包在一起呢。”

小哆啦恍然大悟,赶紧修改代码:“原来是这样,我得传入空字符串 '' 才行。”

改完之后,小哆啦又发现返回值的逻辑不对。小智笑着拍拍小哆啦的肩膀说:“你在 while 循环里,当 temp 等于 neeArr.length - 1 就返回 num,这可不对呀。得等 temp 等于 neeArr.length 时,才说明整个 needle 都找到了,这时候要返回 i,也就是 haystack 里开始匹配的位置。就好比你找宝藏,得找到完整的宝藏才算数,不能只找到一半就说找到了呀。”

小哆啦不好意思地吐吐舌头,再次修改代码,同时还处理了 needle 为空字符串的边界情况。修改后的代码如下:

function strStr(haystack: string, needle: string): number {
    if (needle === '') {
        return 0;
    }
    let hayArr = haystack.split('');
    let neeArr = needle.split('');
    for (let i = 0; i <= hayArr.length - neeArr.length; i++) {
        let num = i;
        let temp = 0;
        while (num < hayArr.length && temp < neeArr.length && hayArr[num] === neeArr[temp]) {
            num++;
            temp++;
        }
        if (temp === neeArr.length) {
            return i;
        }
    }
    return -1;
}

这次运行代码,结果终于正确啦。

寻求优化,邂逅 KMP

不过,小智又发话了:“小哆啦,你这个方法虽然能解决问题,但是当字符串很长的时候,效率就有点低啦。就好比你在一个超级大的迷宫里找东西,一个一个房间地找,速度肯定慢。我们可以用更厉害的 KMP 算法来优化。”

小哆啦一听,眼睛里闪着好奇的光:“KMP 算法?听起来好厉害,快给我讲讲。”

小智耐心地解释:“KMP 算法呀,就像是有一个神奇的地图,能让你在找 needle 的时候,跳过一些已经检查过的地方。我们先对 needle 做个预处理,算出一个 next 数组,这个数组能记录 needle 里每个位置前面子串的最长公共前后缀长度。当匹配的时候遇到不匹配的情况,就根据 next 数组移动 needle,这样就能少做很多比较啦。”

成功优化,收获成长

小哆啦认真地听着,然后在小智的帮助下,写出了使用 KMP 算法的代码:

function strStr(haystack: string, needle: string): number {
    if (needle === '') {
        return 0;
    }
    const m = haystack.length;
    const n = needle.length;
    const next = computeNext(needle);
    let i = 0; 
    let j = 0; 
    while (i < m) {
        if (needle[j] === haystack[i]) {
            i++;
            j++;
        }
        if (j === n) {
            return i - j;
        } else if (i < m && needle[j] !== haystack[i]) {
            if (j !== 0) {
                j = next[j - 1];
            } else {
                i++;
            }
        }
    }
    return -1;
}

function computeNext(needle: string): number[] {
    const n = needle.length;
    const next: number[] = new Array(n).fill(0);
    let len = 0;
    let i = 1;
    while (i < n) {
        if (needle[i] === needle[len]) {
            len++;
            next[i] = len;
            i++;
        } else {
            if (len !== 0) {
                len = next[len - 1];
            } else {
                next[i] = 0;
                i++;
            }
        }
    }
    return next;
}

小哆啦运行优化后的代码,速度明显快了很多,兴奋得跳了起来:“哇,KMP 算法太厉害啦,就像给我的代码装上了火箭发动机!”

结语

经过这次挑战,小哆啦深刻体会到了编程世界的奇妙与深邃。每一次错误都是成长的基石,每一次优化都是向更高峰的攀登。在编程小镇里,小哆啦的名气随着这次成功的挑战越来越大,但他知道,这只是漫漫编程路上的一小步。未来,还会有更多复杂的难题、更神奇的算法等待着他去探索。小哆啦怀揣着对编程的热爱与执着,期待着下一次充满惊喜与挑战的编程冒险,因为他坚信,在代码的星辰大海中,总有新的宝藏等待他去发现!