代码随想录算法训练营第9天| 字符串2 28. 实现 strStr() | 459.重复的子字符串 | 字符串总结+双指针总结

28. 实现 strStr() (本题一刷可以跳过)(基本了解KMP,需要二刷

刷题建议:因为KMP算法很难,第一遍了解大概思路,二刷的时候,再看KMP会 好懂很多。
题目链接:28. 实现 strStr()
文章讲解/视频讲解:28. 实现 strStr()

思路

用KMP算法,将暴力解法的时间复杂度O(m*n)降低为O(m+n)
KMP算法主要是解决字符串匹配问题的
本体主要分为2步:

  1. 构造Next[]数组也就是前缀表
    • 初始化
    • 处理前后缀不相同的情况
    • 处理前后缀相同的情况
  2. 利用next数组来做匹配在文本串s里 找是否出现过模式串t。
  • 初始化:定义两个下标j 指向模式串起始位置,i指向文本串起始位置
  • 匹配过程
    • 如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置
    • 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动
      如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
class Solution {
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);
        // 下标j 指向模式串起始位置,i指向文本串起始位置
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            // s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置
            while(j > 0 && needle.charAt(j) != haystack.charAt(i))
                j = next[j - 1];
            // 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动
            if(needle.charAt(j) == haystack.charAt(i))
                j++;
            // 如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了
            // 所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
            if(j == needle.length())
                return i - needle.length() + 1;    
        }
        return -1;
    }

    private void getNext(int[] next, String s){
        int j = 0;
        next[0] = 0;  // 初始化next[]数组
        for(int i = 1; i < s.length(); i++){
            while(j > 0 && s.charAt(j) != s.charAt(i)) // 前后缀不同
                j = next[j - 1];  // 向前回退
            if(s.charAt(j) == s.charAt(i)){ // 找到相同的前后缀
                j++;
            }
            next[i] = j;  // // 将j(前缀的长度)赋给next[i]
            
        }
    }
}

459.重复的子字符串 (本题一刷可以跳过)(需要二刷

本题算是KMP算法的一个应用,不过 对KMP了解不够熟练的话,理解本题就难很多。
我的建议是 KMP和本题,一刷的时候 ,可以适当放过,了解怎么回事就行,二刷的时候再来硬啃
题目链接:459.重复的子字符串
文章讲解/视频讲解:459.重复的子字符串

KMP解法

在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串
next 数组记录的就是最长相同前后缀(这里的next数组是以统一不减一的方式计算的)
数组长度为:len。
如果数组长度len % (len - (next[len - 1] )) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。(也就是说数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。

解题步骤

  1. 构造 next 数组
  2. 判断是否是重复的子字符串,即判断数组长度len % (len - (next[len - 1] )) == 0
// 代码随想录  KMP
class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s.equals("")) return false;

        int len = s.length();
        // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
        s = " " + s;
        char[] chars = s.toCharArray();
        int[] next = new int[len + 1];

        // 构造 next 数组过程,j从0开始(空格),i从2开始
        for (int i = 2, j = 0; i <= len; i++) {
            // 匹配不成功,j回到前一位置 next 数组所对应的值
            while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
            // 匹配成功,j往后移
            if (chars[i] == chars[j + 1]) j++;
            // 更新 next 数组的值
            next[i] = j;
        }

        // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
        if (next[len] > 0 && len % (len - next[len]) == 0) {
            return true;
        }
        return false;
    }
}
// 代码随想录  移动匹配
public class Solution {
    public boolean repeatedSubstringPattern(String s) {
        String t = s + s;
        t = t.substring(1, t.length() - 1); // 掐头去尾
        return t.contains(s);
    }
}

遗留问题

这道题视频中KMP的具体推导过程不太理解,需要二刷

字符串总结篇

文章讲解:[字符串总结]
(https://programmercarl.com/%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%80%BB%E7%BB%93.html )

双指针法是字符串处理的常客
KMP算法是字符串查找最重要的算法

双指针总结

文章讲解:双指针

文章中一共介绍了leetcode上九道使用双指针解决问题的经典题目,除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为 O ( n ) O(n) O(n)

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个程序实现以上要求的示例: ```c++ #include <iostream> #include <cstring> using namespace std; int main() { string input_str = "ab12cd34ef56"; string result_str; string compare_str; bool has_number = false; //找出数字并拼装新字符串 for(int i=0; i<input_str.length(); i++) { if(isdigit(input_str[i])) { has_number = true; result_str += input_str[i]; } else { result_str += "_"; } } //如果没有数字,直接返回空字符串 if(!has_number) { return 0; } //扣出要比较的字符串并反转 for(int i=result_str.length()-1; i>=0; i--) { if(result_str[i] != '_') { compare_str += result_str[i]; } else { break; } } //逐个比较或者使用strstr函数,找到为止 for(int i=0; i<input_str.length(); i++) { if(input_str[i] == compare_str[0]) { bool is_match = true; for(int j=0; j<compare_str.length(); j++) { if(input_str[i+j] != compare_str[j]) { is_match = false; break; } } if(is_match) { cout << "匹配成功!" << endl; return 0; } } } cout << "没有匹配的字符串!" << endl; return 0; } ``` 这个程序的输入是一个字符串,输出是找到的匹配字符串或者没有匹配的提示信息。程序的执行步骤如下: 1. 遍历输入字符串,找出其中的数字,并将数字拼装成一个新的字符串(result_str),不是数字的字符用"_"代替。 2. 如果没有数字,直接返回空字符串。 3. 从result_str中扣出要比较的字符串(compare_str),即从右往左数,遇到"_"停止。 4. 将compare_str反转。 5. 逐个比较input_str中的字符是否与compare_str的第一个字符相同,如果相同,再逐个比较接下来的字符是否匹配,如果匹配,输出匹配成功并结束程序。 6. 如果遍历完input_str后仍然没有匹配的字符串,输出没有匹配的提示信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值