28 找出字符串中第一个匹配项的下标
题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
方法一:暴力匹配
自己写的
主要思路还是以遍历haystack为中心,遇到第一个和needle相同的元素记录下当前的位置,然后同时遍历haystack和needle,如果有不同直接跳出,继续遍历到haystack的当前位置的下一个元素,依次循环即可
class Solution {
public int strStr(String haystack, String needle) {
int i = 0;
while(i < haystack.length()){
char c = needle.charAt(0);
int j = 0;
int temp = i;
if(haystack.charAt(i) == c){
while(j < needle.length() && i < haystack.length()){
if(haystack.charAt(i) != needle.charAt(j)){
break;
}
i++;
j++;
if(j == needle.length()){
return temp;
}
}
}
i = temp + 1;
j = 0;
}
return -1;
}
}
官方写的
代码中第8行的 i + m <= n 很巧妙,取了极端情况就是needle完全在haystack的最后面
hello ,llo 这种情况满足,其他情况就全满足、
第二个巧妙的地方就是11行的 i + j
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
char[] a = haystack.toCharArray();
char[] b = needle.toCharArray();
boolean flag = true;
for(int i = 0; i + m <= n; i++){
flag = true;
for(int j = 0; j < m; j++){
if(a[i + j] != b[j]){
flag = false;
break;
}
}
if(flag){
return i;
}
}
return -1;
}
}
KMP(算法解决的就是字符串匹配的问题)
应用:KMP主要应用在字符串匹配上
核心:KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。****
所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任
前缀表
next数组就是一个前缀表(prefix table),前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
重点:
记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
因为前缀表要求的就是相同前后缀的长度
**kmp算法整体流程**
https://code-thinking.cdn.bcebos.com/gifs/KMP%E7%B2%BE%E8%AE%B22.gif
i指向后缀末尾,j指向前缀末尾
j还代表这个i之前包括i子串的最长相等前后缀的长度
0 1 0 1 2 0
s : a a b a a f
j i
初始化:j = 0;(前缀开始位置)
next[0] = 0;(第一个元素的最长公共子串一定是0)
i 的初始化要从1开始,for(i = 1; i < s.length(); i++){ }
处理前后缀不相同的情况:s [ i ] != s [ j ]
s: a a b a a f
j i
此时前缀是aa,后缀是ab,此时j看前一位下标是0,就回退到0位置
j = next[j - 1]; 因为j - 1 ,所以 j > 0,
while( j > 0 && s [ i ] != s [ j ]) 回退一次很有可能s[ i ] 依然 != s[ j ]
j = next[j - 1]
前后缀相同情况
if(s[ i ] == s[ j ]){}
例子;起始时j指向0位置,i指向1位置 s[ i ] == s[ j ] ,让 j++,此时jj还代表这个i之前包括i子串的最长相等前后缀的长度(注意i的位置还没有变),此时更新next数组的值next[ i ] = j; 之后 i++(不用写,因为i++已经在for循环中++了)
填充next数组伪代码:重点看含义
void getNext(int[] next ,String s){
int j = 0;
next[0] = 0;
for(int i = 0; i < s.length(); i++){
while(j > 0 && s[i] != s[j]){
j = next[j - 1];
}
if(s[i] == s[j]){
j++;
}
next[i] = j;
}
}
得到的next数组就是完整的前缀表,没做任何移动,之后拿next数组原封不动的做模式串去匹配文本串
class Solution {
//前缀表(不减一)Java实现
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
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;
}
}
}
代码中为啥15 行那么写,举个例子就明白了
a b a a c
a a
最后结果i是指向3位置,况且一定是下面字符串先遍历完,所以i位置 - 下面字符串 + 1 就是要求的位置;
自己根据理解写的
class Solution {
public int strStr(String haystack, String needle) {
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for(int i = 0; i < haystack.length(); i++){
while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
j = next[j - 1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j == needle.length()){
return i - needle.length() + 1;
}
}
return -1;
}
public void getNext(int[] next, String s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(i) != s.charAt(j)){
j = next[j - 1];
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;
}
}
}
459.重复的子字符串
题目链接:https://leetcode.cn/problems/repeated-substring-pattern/
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
方法一
当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:
也就是由前后相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:
所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
class Solution {
public boolean repeatedSubstringPattern(String s) {
String str = s + s;
return str.substring(1, str.length() - 1).contains(s);
}
}
KMP方法
s: a b a b a b a b
0 0 1 2 3 4 5 6
next[len - 1] 这个位置代表最长相等前后缀,一定注意与上一道题想区分,上一道题是b 和 f 不一样时找前一个位置的next,这道题可不是冲突,直接就看next[len - 1]就代表了最长公共子串的长度;
也就是说长度上当前位置找,字符串比较冲突时,看前一个位置元素的next值找位置
class Solution {
public boolean repeatedSubstringPattern(String s) {
int[] next = new int[s.length()];
getNext(next, s);
int len = s.length();
if(next[len - 1] != 0 && len % (len - (next[len - 1])) == 0){
return true;
}
return false;
}
public void getNext(int[] next, String s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(i) != s.charAt(j)){
j = next[j - 1];
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;
}
}
}