一、28实现strStr()
先讲讲KMP,具体是在代码随想录中详细讲解
- 什么是KMP
- KMP有什么用
- 什么是前缀表
- 为什么一定要用前缀表
- 如何计算前缀表
- 前缀表与next数组
- 使用next数组来匹配
- 时间复杂度分析
- 构造next数组
- 使用next数组来做匹配
- 前缀表统一减一 C++代码实现
- 前缀表(不减一)C++实现
- 总结
找到前缀表010120,做出next表,这个next表就是没有任何的操作,就是等于前缀表,所以KMP的核心在于找到next数组,然后比较的时候,这里都是用j来比较 i 的,如果发现这一位不匹配,那么就是 j 要回退到前一位的next数组的值的那一位,即 j = next[j - 1],并且这里是做循环while的(这里我们定义的是 i 是后缀末尾,j 是前缀末尾)。然后这一位再进行比较,相等的话 j++,然后给next数组赋值 j ,即next[i] = j。然后根据这层逻辑求出来next数组,这里就是下边写到的getNext函数的定义,在主函数中直接传入初始化的next数组和模式串到getNext函数中,然后进行主串和模式串的比较
然后写代码,在代码中首先要判断一下如果模式串为0或者模式串的长度大于主串的长度,那么会返回-1
之后就是初始化创建一个next数组,然后带入上述已经写好的getNext函数中,得出next数组
然后进行主串和模式串的比较,先初始化 j = 0,然后进入for循环,从模式串的 j 位比较主串的 i 位,如果不相同,类似getNext写一个while循环,然后 j 移动到前一位的next位,即 j = next[j - 1]。然后如果相同的话,写if,就是 j 移动到下一位,即 j++。最后还有一个最后的if,要判断是否是比较完成了,比较完成根据题意返回主串匹配成功的开始位,这里因为比较完成之后j是++的,所以j是在模式串的最后一位+1的位置的,所以判断的时候直接比较 j == needle.length(),如果相等的话就是返回 return i - needle.length() +1,这个就是匹配成功的开始位。代码如下:
class Solution {
public int strStr(String haystack, String needle) {
if(needle.length() == 0)
return -1;
if(haystack.length() < needle.length())
return -1;
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;
}
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(j) != s.charAt(i)){
j = next[j - 1];
}
if(s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
}
二、459重复的子字符串
首先这道题先要清楚是求什么,求的是这一串给定字符串是不是由重复的子字符串组成的,如果是就返回true,所以定义的函数类型是boolean型的,之后开始下面的代码
首先要写一个判断 s 是不是空串即使用一个equals函数:s.equals(""),之后s的长度赋值给len,再往下将s转换为字符串数组:利用char[] chars = s.toCharArray()来转换,其实这一步和 s.charAt(i) 这个效果是一样的。之后创建一个next数组,要和自己串的长度相等,之前上一题不和主串长度相等是因为这道题这个串自己就是子串。
之后就是求next数组的时候了,其实步骤和之前的上一题求next数组方法是一样的,只不过在进入for循环的时候要先初始化 j = 0;next[0] = 0;之后就是求next数组的固定过程了,就是while循环是为了可以让 j 回退,然后 i 从1的位置开始,和j位置的值进行比较,如果不相等的话,j 就要回退到前一位的next数组的值的那一位,以此循环,如果发现相等,也就是下一步就要判断的,j要++
这里注意要直接跳出if循环 ,然后给next数组赋值,而且if条件不能加j>0了,因为
j > 0
的目的是确保j
不会超出数组next
的索引范围。但是,j
的值是可以为0的,特别是在循环的初始阶段。如果在j
的值为0时不允许执行j++
,那么在next[0] = 0;
之后就永远无法使j
的值增加,导致进入死循环。
之后就是最关键的一步了,要判断这个字符串是不是重复字符串,就是要判断字符串的长度减去next数组的最后一位的值,也就是最长相等前后缀的长度,然后看字符串的长度是否能整除,能整除的话就是重复字符串,并且这里还要加一个next[len - 1] >0的条件,原因如下:
KMP 算法的
next[len - 1]
表示了字符串中最长的相同前缀后缀的长度。如果这个值大于 0,说明存在相同的前缀后缀,即字符串中存在重复的模式,因为你找的时候不就是从前面找到后面的,后面都是基于前面的,如果最后一位都没找着,那之前的那几位都没有的,很简单
如果 都没找到最后返回一个false,代码如下:
class Solution {
public boolean repeatedSubstringPattern(String s) {
if(s.equals(""))
return false;
int len = s.length();
char[] chars = s.toCharArray();
int[] next = new int[len];
int j=0;
next[0] = 0;
for(int i=1;i<len;i++)
{
while(j>0&&chars[i] != chars[j])
{
j = next[j-1];
}
if(chars[i] == chars[j])
j++;
next[i] = j;
}
if(next[len - 1] > 0 && len % (len - next[len - 1]) == 0)
{
return true;
}
return false;
}
}
三、字符串总结
主要是双指针法,以及KMP算法的地方
双指针法在数组、链表以及字符串中很常用
KMP算法主要是理解最长相等前后缀概念是什么,怎么拿笔求出来的,之后又怎么用next数组求出来的,在匹配字符串的时候很实用
字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力。
双指针法是字符串处理的常客。
KMP算法是字符串查找最重要的算法,但彻底理解KMP并不容易,我们已经写了五篇KMP的文章,不断总结和完善,最终才把KMP讲清楚。(摘自代码随想录)
四、双指针回顾
- 27移除元素 详情见 day1的移除元素:快慢指针一起动,快指针for循环;判断条件后不相等边的值向前覆盖变相删除 之后慢指针++ 下一轮循环
- 344反转字符串 详情见 day7的反转字符串:头尾指针 头指针++ 尾指针-- 异或法
- 替换数字 详情见 day7的替换数字:这个Java代码使用的是开辟新空间进行填充,返回新数组
- 151反转字符串里的单词 详情见 day7的翻转字符串里的单词:三部分 ①去首尾空格中间多余空格用首尾双指针 ②翻转整个字符串用首尾双指针 ③翻转空格之间的字符串用首双指针,一个是首位一个是下一位,尾指针循环+1,一直判断到是否有空格停止,再套用第二步定义的反转字符串函数
- 206反转链表 详情见 day3反转链表:一个pre节点在cur节点前面,一个temp节点在cur节点后面,然后开始移动,顺序是temp先移动到cur.next,然后打断链表,cur.next指向pre,之后pre指向cur,cur再指向temp,之后进入下一轮循环,一直到cur指向temp指向null
- 24两两交换链表中的节点 详情见 day4两两交换链表中的节点:这道题设置了四个节点指针,首先是创建了一个虚拟头节点,让他指向头节点,之后四个指针节点分别指向虚拟头节点,第一二三节点,移动顺序是先将cur指向second,这是定头,再将second指向first,之后再将first指向temp也就是第三个节点
- 19删除链表的倒数第N个节点 详情见 day4删除链表的倒数第N个节点:这道题首先定义了一个虚拟头节点指向head,双指针:两个快慢指针都指向虚拟头节点,之后让快指针移动n+1个位置,保证和慢指针之间的距离保持n,两个指针同时向后移动,直到快指针指向null,那么慢指针指向的就是倒数第n个位置,然后做删除操作即可
- 链表相交 详情见 day4面试题-链表相交:这道题定义了两个链表的头指针nodea和nodeb,然后将两链表的尾端对齐,将链表a的头节点指针移动lena-lenb的距离,也就是和b链表的头节点指针对齐开始比较,如果相等就直接返回相等的节点即可,如果不相等那就继续循环比较
- 142环形链表II 详情见 day4环形链表II:首先要定义快慢指针都从头节点开始,每次快指针向后移动两个单位,慢指针向后移动一个单位,如果有环的话他们一定会相遇的,这是一个定理;之后在相遇的时候又定义了一个头节点开始的指针,一个相遇点的指针,然后两个一起向后移动,如果能相遇的话,说明这是环的入口,这个也是一个定理,是推导出来的,不管那个相遇点转几圈,他都会和从头开始的指针在环的入口相见
- 15三数之和 详情见 day6三数之和:这道题是想要找到给定数组里的和0的不重复三元组,那么这道题先对数组进行了从小到大排序,之后去掉重复的a,此时位置是在i,然后定义了i+1的位置的指针向后移动,定义了最后位置的指针(即b、c)向前移动进行相加统计和,和如果为0就添加到三元组,之后去掉b、c的下一位重复值,再移动去找和为0的元组,最后返回
- 18四数之和 详情见 day6四数之和:四数之和可以参考三数之和,因为他只多了一个for循环 j = i+1的循环操作,相当于就是i做循环,然后再从i的下一位做循环,再判断去重,其他过程都是和之前三数之和是一样的,只是多了一个i下一位的指针
以上这些最好画个图去理解,画一下指针的动态变化,还有链表打断重新指向的过程