返回

暴击力学:用KMP武装你的暴力解法,轻松拿下LeetCode28!

后端

引子:暴力解法的疾风骤雨

暴力解法以其简单粗暴的搜索方式,在字符串匹配的世界中闯出了一片天地。它就像一位蛮力十足的战士,用最直接的方式寻找目标。算法的核心思想是:

  1. 依次枚举haystack中的每个字符作为子字符串needle的起始位置。
  2. 对于每个起始位置,比较haystack和needle的子串,如果匹配,则返回起始位置;如果不匹配,则继续枚举下一个起始位置。

KMP算法:从暴力中升华的优雅

KMP算法,全称为Knuth-Morris-Pratt算法,是一种字符串匹配算法,以其高效和广泛的适用性而著称。它在暴力解法的基础上,加入了一个巧妙的优化策略——前缀表 。前缀表记录了needle中每个前缀与needle本身的最长公共前缀的长度。利用前缀表,我们可以快速跳过不匹配的部分,从而大幅提升搜索效率。

算法流程:化繁为简,势如破竹

KMP算法的流程可以概括为以下几个步骤:

  1. 预处理needle,构建前缀表。
  2. 将needle和haystack拼接成一个新的字符串。
  3. 使用一个指针i遍历拼接后的字符串。
  4. 将i处的字符与needle的相应字符比较。
  5. 若匹配,则i加1,继续比较下一个字符。
  6. 若不匹配,则根据前缀表,将i回退到needle中与当前字符匹配的最长公共前缀的末尾。
  7. 重复步骤4-6,直至遍历完拼接后的字符串。

代码实现:将算法之美化为代码之实

class Solution {
    /**
     * 使用KMP算法实现字符串匹配
     *
     * @param haystack 主串
     * @param needle   子串
     * @return 子串在主串中第一次出现的位置,如果没有出现则返回 -1
     */
    public int strStr(String haystack, String needle) {
        if (needle.isEmpty()) {
            return 0;
        }

        // 构建前缀表
        int[] prefixTable = buildPrefixTable(needle);

        // 使用KMP算法匹配字符串
        int i = 0;
        int j = 0;
        while (i < haystack.length()) {
            if (haystack.charAt(i) == needle.charAt(j)) {
                i++;
                j++;
                if (j == needle.length()) {
                    return i - j;
                }
            } else {
                if (j > 0) {
                    j = prefixTable[j - 1];
                } else {
                    i++;
                }
            }
        }

        return -1;
    }

    /**
     * 构建前缀表
     *
     * @param needle 子串
     * @return 前缀表
     */
    private int[] buildPrefixTable(String needle) {
        int[] prefixTable = new int[needle.length()];
        prefixTable[0] = 0;
        int i = 1;
        int j = 0;
        while (i < needle.length()) {
            if (needle.charAt(i) == needle.charAt(j)) {
                prefixTable[i] = j + 1;
                i++;
                j++;
            } else {
                if (j > 0) {
                    j = prefixTable[j - 1];
                } else {
                    prefixTable[i] = 0;
                    i++;
                }
            }
        }

        return prefixTable;
    }
}

结语:从暴力到优雅,算法之旅不止于此

在LeetCode28题中,我们从暴力解法入手,引入了KMP算法,用更优雅的方式解决了字符串匹配问题。无论是暴力解法还是KMP算法,都体现了算法设计中的巧妙与智慧。算法的世界无穷无尽,期待你在LeetCode的征途中不断探索,不断进步,不断创造属于自己的算法传奇!