LeetCode.28 实现 strStr()

223 阅读3分钟

这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战

题目描述:

28. 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)

实现 strStr() 函数。

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

示例一

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

示例二

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

说明

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

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

思路分析

暴力匹配

这个就很容易了啊,第一次遍历父串haystack如果找到第一个匹配则开始进入子串needle的遍历

如果匹配失败就立即停止字串的匹配

如果匹配成功,当前字串的起始位置即为答案。

AC代码

class Solution {
    fun strStr(haystack: String, needle: String): Int {
        if (needle == "") return 0
        //到最后一段needle长度的起点为最后一趟检查 后面就长度不够了
        for (i in 0..haystack.length-needle.length) {
            for (j in needle.indices) {
                if (haystack[i+j] != needle[j] ) break
                else if (j == needle.length-1) {
                    return i
                }
            }
        }
        return -1
    }
}

KMP

这一题可以说是非常经典的字符串匹配问题之一了,说到字符串匹配我们常常提到KMP算法,其他类似的还有BM,Horspool等。

KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

至于具体的算法过程,这里就不赘述了,题解区很多大神对KMP的讲解都很到位,可以去学习一下。

AC代码

//答案参考的题解区的,连接在下方。
class Solution {
    fun strStr(haystack: String, needle: String): Int {
        // 两种特殊情况
        if (needle.isEmpty()) return 0
        if (haystack.isEmpty()) return -1

        // char 数组
        val haystackArray = haystack.toCharArray()
        val needleArray = needle.toCharArray()
        // 长度
        val m = haystackArray.size
        val n = needleArray.size
        // 返回下标
        return kmp(haystackArray, m, needleArray, n)
    }

    /**
     * 第二步:根据 next 数组直接匹配
     *
     * @param haystack
     * @param m
     * @param needle
     * @param n
     * @return
     */
    fun kmp(haystack: CharArray, m: Int, needle: CharArray, n: Int): Int {
        // 获取 next 数组
        val next = next(needle, n)
        var j = 0
        // 注意 i 就从0开始
        for (i in 0 until m) {
            // 不匹配
            while (j > 0 && haystack[i] != needle[j]) {
                // 移动位数 = 已匹配的字符数 - 最后一个匹配字符对应的部分匹配值 -> j = j - (j - next[j - 1])
                // 寻找 j 之前匹配的位置 = 已匹配的字符数 - 移动位数 = j = j - (j - next[j - 1]) = next[j - 1]
                j = next[j - 1]
            }
            // 如果相同就将指针同时后移一下,比较下个字符
            if (haystack[i] == needle[j]) {
                j++
            }
            // 遍历完整个模式串,返回模式串的起点下标
            if (j == n) {
                return i - n + 1
            }
        }
        return -1
    }

    /**
     * 第一步:构建 next 数组(部分匹配表)
     *
     * @param needle
     * @param len
     * @return
     */
    fun next(needle: CharArray, n: Int): IntArray {
        // 定义 next 数组
        val next = IntArray(n)
        // 初始化
        next[0] = 0
        // 快慢指针 i 和 j
        var i = 0
        for (j in 1 until n) {
            // 当前后缀不相同时(needle[i + 1] != needle[j]),则回溯,找 next[i]
            // eg:aabaaac : aabaa -> aa ;aabaaa -> needle[2]!=needle[5],i=next[1]=0,needle[i+1=1]==needle[5],aa
            while (i > 0 && needle[i] != needle[j]) {
                i = next[i - 1]
            }
            // 找到相同的前后缀(needle[i + 1] == needle[j]):已知 [0,i-1] 的最长前后缀;然后 k - 1 又和 i 相同,最长前后缀加 1
            if (needle[i] == needle[j]) {
                i++
            }
            next[j] = i
        }
        return next
    }
}

总结

这题的主要目的还是在于对经典字符串匹配算法的认知,像KMP,BM啊都是非常经典的解法,需要多多熟悉了然于心。

参考

实现 strStr() - 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)

轻松解决! 几行代码 一大一小循环 kotlin - 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)

「代码随想录」KMP算法详解 - 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)

KMP 算法(Kotlin) - 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)

28. 实现 strStr() - 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)