day08 28. 实现 strStr()&&459.重复的子字符串

1.问题描述
实现 strStr() 函数,返回子字符串 needle 在字符串 haystack 中首次出现的位置,如果不存在,则返回 -1。题目要求实现两种算法:
暴力算法:逐个比较子串。
KMP 算法:利用前缀表(next 数组)优化匹配过程。
暴力算法
暴力算法的核心思想是逐个检查 haystack 中的每个可能的子串,看是否与 needle 匹配。
逻辑分析
边界条件:
如果 haystack 或 needle 为 null,返回 -1。
如果 haystack 的长度小于 needle 的长度,返回 -1。
逐个匹配:
遍历 haystack 的每个可能的子串起始位置 i。
对于每个起始位置,逐个字符比较 needle 和子串,直到不匹配或匹配成功。
时间复杂度:
最坏情况下为 O((n - m + 1) * m),其中 n 是 haystack 的长度,m 是 needle 的长度。
KMP 算法
KMP 算法通过构建 next 数组(前缀表)来优化匹配过程。next 数组记录了模式串 needle 的每个位置的最大公共前后缀长度。
逻辑分析
next 数组的作用:
记录模式串 needle 的每个位置的最大公共前后缀长度。
用于在匹配失败时快速跳过已匹配的部分,避免重复比较。
匹配过程:
使用两个指针 i 和 j 分别遍历 haystack 和 needle。
如果字符匹配,i 和 j 同时向右移动。
如果不匹配,j 回退到 next[j - 1],i 保持不变。
如果 j 达到 needle 的长度,说明匹配成功,返回起始位置。
时间复杂度:
构建 next 数组的时间复杂度为 O(m)。
匹配过程的时间复杂度为 O(n),整体时间复杂度为 O(n + m)。
//28. 实现 strStr()
public int strStr(String haystack, String needle) {
//needle:模式子串,暴力算法
/if (haystack == null || needle == null) {
return -1;
}
if (haystack.length() < needle.length()) {
return -1;
}
char[] c1 = haystack.toCharArray();
char[] c2 = needle.toCharArray();
for (int i = 0; i < c1.length - c2.length + 1; i++) {
int j;
for (j = 0; j < c2.length; j++) {
if (c2[j] != c1[i + j]) break;
}
if (j == c2.length) {
return i;
}
}
return -1;
/
//kmp算法
if (haystack == null || needle == null) {
return -1;
}
if (needle.isEmpty()) {
return 0; // 如果 needle 是空字符串,按照题意返回 0
}
if (haystack.length() < needle.length()) {
return -1;
}
char[] c1 = haystack.toCharArray();
char[] c2 = needle.toCharArray();
int[] next=next(needle);
int j=0;
// 循环判断条件优化前:i<c1.length
for (int i = 0;next.length-j <= c1.length-i;) {
if(c1[i]c2[j]){
i++;
j++;
if (j
next.length)return i-j;
}else {
if (j0){
i++;
}else {
//此时的i与j是匹配失败的,也就是说被匹配串(母串)的0-i部分是没有next数组中0-j索引构成的子串的,
// 而是只有0-(j-1)索引构成的子串,故不能使用next[j]获取最大公共前后缀(即直接获取j索引记录的值),而是应该使用next[j-1]获取
j=next[j-1];
}
}
}
return -1;
}
//next数组,记录每个位置的最大公共前后缀,最大公共前后缀只与模式子串有关
private static int[] next(String pattern){
char[] chars = pattern.toCharArray();
int[] next = new int[chars.length];
int j=0,i=1;
while (i < next.length){
if (chars[i]
chars[j]){
next[i]=++j;
i++;
}else {
if (j==0){
//res数组中默认值为0,所以没有最大公共前缀时,直接移动索引i即可
i++;
}else {
//此时i索引指向的位置还没有处理,所有不要动i
//而j前面的元素已经匹配过了,所以j就回到前一个位置找最大公共前后缀,
// 然后j就回到最大公共前后缀(前缀匹配成功的位置)进行下一轮循环,重新跟i比较
j=next[j-1];
}
}
}
return next;
}

2.问题描述
题目 459. 重复的子字符串 要求判断一个字符串是否可以由它的一个子串重复多次拼接而成。例如,字符串 "abab" 可以由子串 "ab" 重复两次拼接而成,因此返回 true;而字符串 "aba" 无法通过重复某个子串得到,因此返回 false。
代码实现分析
你提供了两种实现方式:
暴力解法(未优化):
逐个尝试所有可能的子串长度。
对每个可能的子串长度,检查是否可以通过重复该子串得到原字符串。
逻辑复杂,容易出错,且时间复杂度较高,容易超时。
优化后的解法:
通过数学方法减少不必要的检查:只有当子串长度是原字符串长度的因数时,才进行检查。
使用模运算简化了重复子串的验证逻辑。
优化后的代码
//459.重复的子字符串
public boolean repeatedSubstringPattern(String s) {
//答案正确,但时间超出限制且逻辑复杂易出错
/*int len = s.length();
int start2=1;
char[] chars = s.toCharArray();

    while (start2<=len-start2) {
        boolean flag = true;//用于记录此时的子串是否能重复一次
        for (int i = 0; i < start2; i++) {
            if (chars[i] != chars[start2 + i]) {
                start2++;
                flag = false;
                break;
            }
        }
        if (len==2*start2&&flag) return true;//字符串s刚好能重复两次子串
        int index=0;
        boolean flag2=true;//用于记录此时的子串是否能重复多次
        for ( int i = 2*start2; i < len&&flag; i++) {
            if (chars[index++] != chars[i]) {
                flag2=false;
                start2++;
                break;
            }
            if (index==start2)index=0;
        }
        if (flag2&&index==0&&flag) return true;
    }
    return false;*/
    //优化后代码
    int len = s.length();
    char[] chars = s.toCharArray();
    // 遍历所有可能的子串长度
    for (int subLen = 1; subLen <= len / 2; subLen++) {
        if (len % subLen != 0) continue; // 如果子串长度不是总长度的因数,跳过

        boolean isRepeated = true;//先假设能匹配成功
        // 检查是否可以将整个字符串划分为若干个相同的子串
        for (int i = subLen; i < len; i++) {
            if (chars[i] != chars[i % subLen]) {
                isRepeated = false;
                break;
            }
        }
        if (isRepeated) return true; // 如果找到符合条件的子串,直接返回 true
    }
    return false; // 如果遍历完所有可能的子串长度仍未找到符合条件的子串,返回 false
}

优化后的逻辑分析
减少不必要的检查:
只检查那些长度是原字符串长度因数的子串。例如,对于字符串 "abab",长度为 4,只检查长度为 1 和 2 的子串。
这样可以避免检查那些不可能构成重复子串的长度。
高效验证重复子串:
使用模运算 i % subLen 来验证重复子串。
如果某个字符 chars[i] 不等于对应的子串字符 chars[i % subLen],则说明当前子串长度无法构成重复子串,直接跳过。
时间复杂度:
最坏情况下,时间复杂度为 O(n²),其中 n 是字符串的长度。
但在实际运行中,由于减少了不必要的检查,性能显著优于暴力解法。
总结
优化前的暴力解法:
逻辑复杂,容易出错。
时间复杂度较高,容易超时。
优化后的解法:
通过数学方法(检查因数)减少了不必要的子串长度检查。
使用模运算简化了重复子串的验证逻辑。
代码更简洁,性能更优。
关键点:
数学优化:只检查长度为原字符串长度因数的子串。
模运算:通过 i % subLen 快速验证重复子串。
进一步优化:
如果需要进一步优化,可以考虑使用字符串拼接的方法(如 s + s,然后检查是否包含原字符串 s),但这会增加空间复杂度。

posted @ 2025-01-22 20:50  123木头人-10086  阅读(102)  评论(0)    收藏  举报