【每日算法】Day 3-2:字符串匹配终极武器——KMP算法详解(C++实现)

突破字符串匹配难题!今日深入解析KMP算法核心思想,从暴力匹配到next数组优化,彻底掌握高效字符串匹配的实现原理与代码技巧。

一、KMP算法核心思想

KMP算法(Knuth-Morris-Pratt)是高效的字符串匹配算法,其核心思想:

当模式串与主串不匹配时,利用已匹配的部分信息跳过不必要的比较,避免暴力匹配的完全回溯。

算法特性:

  • 时间复杂度:O(n + m)(主串长n,模式串长m)

  • 空间复杂度:O(m)(存储部分匹配表)

  • 核心组件:部分匹配表(next数组)


二、算法原理与步骤分解

1. 部分匹配表(next数组)

定义:
next[i]表示模式串P[0...i]中,最长相等前后缀的长度
示例:
模式串"ABABC"的next数组为[0,0,1,2,0]

2. 匹配流程
  1. 初始化主串指针i=0,模式串指针j=0

  2. 比较S[i]P[j]

    • 匹配成功:i++j++

    • 匹配失败:根据next[j-1]回退j

  3. j == m时匹配成功,否则失败


三、C++代码实现

1. next数组构建(核心)
vector<int> buildNext(string pattern) {
    int m = pattern.size();
    vector<int> next(m, 0);
    int j = 0; // 前缀末尾指针
    
    for (int i = 1; i < m; ++i) {
        // 前后缀不相同,向前回退
        while (j > 0 && pattern[i] != pattern[j]) {
            j = next[j - 1];
        }
        // 前后缀相同,j后移
        if (pattern[i] == pattern[j]) {
            j++;
        }
        next[i] = j;
    }
    return next;
}
2. KMP匹配主体
int kmpSearch(string text, string pattern) {
    vector<int> next = buildNext(pattern);
    int n = text.size(), m = pattern.size();
    int j = 0; // 模式串指针
    
    for (int i = 0; i < n; ++i) {
        // 不匹配时利用next数组回退
        while (j > 0 && text[i] != pattern[j]) {
            j = next[j - 1];
        }
        // 当前字符匹配成功
        if (text[i] == pattern[j]) {
            j++;
        }
        // 完全匹配
        if (j == m) {
            return i - m + 1; // 返回起始位置
        }
    }
    return -1;
}

// 测试用例
int main() {
    string text = "ABABDABACDABABCABAB";
    string pattern = "ABABCABAB";
    cout << "匹配起始位置:" << kmpSearch(text, pattern); // 输出10
    return 0;
}

四、复杂度分析

操作时间复杂度空间复杂度
构建next数组O(m)O(m)
匹配过程O(n)O(1)
总计O(n+m)O(m)

五、大厂真题实战

真题1:重复子字符串(LeetCode 459)

题目描述:
判断字符串s是否由多个重复子串构成
KMP巧解:

bool repeatedSubstringPattern(string s) {
    int n = s.size();
    vector<int> next = buildNext(s);
    return next.back() != 0 && n % (n - next.back()) == 0;
}
真题2:最短回文串(LeetCode 214)

题目描述:
在字符串s前添加字符使其成为回文串,求最短结果
KMP变种解法:

string shortestPalindrome(string s) {
    string rev = s;
    reverse(rev.begin(), rev.end());
    string temp = s + "#" + rev;
    vector<int> next = buildNext(temp);
    return rev.substr(0, rev.size() - next.back()) + s;
}

六、常见误区与优化

  1. next数组索引混乱:注意数组从0开始还是1开始

  2. 回退逻辑错误while循环中必须持续回退

  3. 边界条件处理:空字符串或单字符模式串的特殊处理

  4. 空间优化:可合并next数组构建与匹配过程

  5. 字符集扩展:支持Unicode需改用哈希表存储


七、总结与扩展

核心要点:

  • next数组的构建是算法核心,体现自匹配思想

  • 匹配过程利用已匹配信息避免冗余比较

  • KMP的扩展应用远超字符串匹配(如循环节判断)

扩展思考:

  1. 如何修改算法实现多模式匹配?

  2. KMP与Boyer-Moore算法的性能对比?

  3. 如何利用KMP求字符串的周期?


LeetCode真题练习:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值