代码随想录第九天|28.实现strStr()、459.重复的子字符串、小总结

题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

初见思路:
就是简单的模拟吧,分别判断haystack和needle的长度,分情况进行讨论,定义boolean变量以进行第一个下标的保存,但只通过了73个用例,不知道这是出于什么原因

也没有使用KMP算法,就是非常暴力的逐个求解,不推荐使用

class Solution {
    public int strStr(String haystack, String needle) {
        if(haystack.length() == 0 ){
            return -1;
        }
				if(
        if(needle.length() > 1){
        int res=0;
        int judge = 0;
        boolean first = false;
        for(int i = 0; i < haystack.length();i++){
            if((haystack.charAt(i) == needle.charAt(judge)) && (judge+1 < needle.length()) && (i+1 < haystack.length()) && (haystack.charAt(i+1) == needle.charAt(judge+1))){
                if(!first){
                    res = i;
                    first = true;
                }
                judge++;
                
            }
            else{
                if(judge == needle.length() - 1){
                    return res;
                }
                else{
                    first = false;
                    judge = 0;
                }
            }
        }
        return -1;
    }
    else if(needle.length() == 1){
        for(int i = 0; i < haystack.length();i++){
            if(haystack.charAt(i) == needle.charAt(0)) return i;
            
        }
        return -1;
    }
    else if(needle.length() > haystack.length() ){
        return -1;
    }
    return -1;
}

}

KMP算法:

本质思想:当出现字符串不匹配时,记录之前的一部分已经匹配的文本内容,利用这些信息避免重头再来

正确的做法应当是使用KMP算法,这是由于KMP算法中存在一个next数组,即前缀表,他起到了一个回退的作用,这样做不至于当出现不同时重头再来,而是从一个更加优秀的位置进行重新匹配

前缀表:记录下表i之前(包括i)的字符串中,有多大长度的相同前缀后缀(其实就是为了更快的进行匹配拉)

前缀:不包含最后一个字符的所有以第一个字符开头的连续子串

后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串

next数组的构造:

定义一个函数getNext来构造next数组,函数参数为一个指向next数组的指针和字符串构成

next数组的构造其实就只有三步:

  1. 初始化
    定义两个指针 i 和 j,j指向前缀末尾位置,i指向后缀末尾位置,同时需要对next数组进行初始化赋值,如下

    int j = -1;
    next[0] = j;
    
  2. 处理前后缀不同的情况
    i从1开始进行,否则无意义,进行s[i]与s[j+1]进行比较,若不相同,即此刻为前后缀不相同的情况,此刻需要进行回退,判断 j ≥ 0 ,令 j = next[j]即可

while(j >= 0 && s[i] != s[j+1]){
 j = next[j];
}
  1. 处理前后缀相同的情况
    若s[i] == s[j+1] 则同时向后移动i和j,并修改j未+1时前缀表next[]中的j+1的值
if(s[i] == s[j+1]{
	j++;
}
	next[i] = j;

综上所述

整个getNext函数如下:

public void getNext(int[] next, String s){
//初始化
int j = -1;
next[0] = j;
	for(int i = 1;i < s.length();i++){
		//处理前后缀不相同的情况
			while(j >= 0 && s[j+1] != s[i]){
					j = next[j];
			}
		//处理前后缀相同的情况
		if(s[j+1] == s[i]){
		j++;
		}
		next[i] = j;
	}
}
	

使用next数组进行匹配

定义两个下表 i指向文本串起始位置,j指向模式串起始位置

j初始值仍然为-1,i从0开始遍历文本串,逐个进行比较,若s[i] ≠ t[j+1] 则 j = next[j]; 若s[i] == t[j+1] 则j++

当j == 文本串的长度时,则成功匹配

综上 匹配代码如下:

int j = -1;
for (int i = 0; i < haystack.length();i++){
		if(j >= 0 && haystack.charAt(i) != needle.charAt(j+1){
		j = next[j];
		}
		if( haystack.charAt(i) == needle.charAt(j+1)){
		j++;
		}
		if( j == (t.length() -1)){
		return (i - t.length() -1);
		}
}

故本题ac代码如下:

class Solution {
    public void getNext(int []next, String s){
        int j = -1;
        next[0] = j;
        for(int i = 1;i < s.length();i++){
            while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
                j = next[j];
            }
            if(s.charAt(i) == s.charAt(j+1)){
                j++;
            }
            next[i] = j;
        }
    }
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0) return 0;
        int []next = new int[needle.length()];
        getNext(next,needle);
        int j = -1;
        for(int i = 0; i < haystack.length();i++){
            whlie( (j >= 0) && (haystack.charAt(i) != needle.charAt(j+1))){
                j = next[j];
            }
            if(haystack.charAt(i) == needle.charAt(j+1)){
                j++;
            }
            if(j == needle.length()-1) return i - needle.length()+1;
        }
        return -1;
    }
}

本题目相对较难,需要认真学习kmp算法后才能够体会其中内容


题目链接:459. 重复的子字符串 - 力扣(LeetCode)

初见思路:即求出一个相等前后缀,他们的长度相加是否等于该字符串的长度,若相等则该字符串能够由一个子串重复组成,否则不可以组成

本题中我想的是找到一个最长的相等前后缀,*2后判断长度是否等于原字符串长度,以此来判断是否能够重复组成无疑是无比错误的,如abababab这个字符串 最长相等前后缀为6,这样做忽略了重复的影响,不该这样考虑

正确的做法应当是求出最小的重复子串,即两个最长相等前后缀的不包含的子串,如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w1cjrOjc-1668494909097)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2b7b78c4-83fe-4a79-97b9-9cd3ca2998b1/Untitled.png)]

这里我们设最小重复子串为x 则整个字符串 为 nx 设最长相等前后缀为mx

则必然有 nx - mx = x

所以如果有 n % (n-m) == 0 ,则有最小重复子串能够组成整个字符串
而本题中,若next[len-1]的值不为-1,则说明存在重复的前后缀
其次再判断 length % (length - (next[len-1] + 1)) 的值是否为0,若为0则说明存在最小重复子串能够组成整个字符串

ac代码如下:

class Solution {
    public void getNext(int[] next,String s){
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.length();i++){
            while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
                j = next[j];
            }
            if(s.charAt(i) == s.charAt(j+1)){
                j++;
            }
            next[i] = j;
        }
    }
    public boolean repeatedSubstringPattern(String s) {
        if(s.length() == 0) return false;
        int[] next = new int[s.length()];
        getNext(next,s);

        int len = s.length();
        if(next[len-1] != -1 && len % (len - next[len-1]-1) == 0){
            return true;
        }
        else return false;
    }
}

字符串总结

Java中的String是一个引用类型变量,其值是不可变的,因此我们通常使用StringBuffer来对原有的String类进行操作,常用的获取下标函数为s.charAt(i) ,获取长度使用s.length()

库函数要不要使用?

在解题过程中,若这个函数只是起到很小的一部分作用,且读者本身理解库函数内部的实现原理,那么就可以放心大胆的使用,否则建议不要使用库函数,而是去亲手实现


双指针法:

双指针在数组、链表和字符串中均有广泛的使用,如数组填充等问题,都可以预先扩充数组,随后由后往前进行操作


反转:

  • 当需要按照固定的规律进行处理字符串时,应当想着在for循环上做文章,以此完成固定规律的处理
  • 同时可以通过 整体-局部反转的操作完成诸如左旋、 反转字符串中单词的效果

KMP:

kmp算法解决的核心问题就是子串的匹配问题 其使用的核心就在于next[]前缀表的创建 以及匹配,了解了这两点后并加以思考就能解决大多数的子串匹配问题了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值