Leetcode
替换元素:从后往前的双指针法
在a-b-c中将每一个-替换成任意一个字符串(比如aj),空间开销为O(1)的做法:
首先计算-的数目,算出替换结束后最终字符串会有多大,按新的大小resize字符串
双指针,一个指针指向新字符串的末尾,另一个指向旧字符串的末尾
从后往前遍历
1
2
3
4
5
6
7if(s[oldPtr]!='-'){
s[newPtr--]=s[oldPtr--];
} else {
oldPtr--;
s[newPtr--]='j';
s[newPtr--]='a';
}
删除元素:快慢指针法
在-abc—bbbb-ccc–中删除-
空间开销为O(1)的做法:
快指针往后走,发现不是’-‘的字符,就让慢指针复制这个元素并往后走一步
但是这样会把-完全删掉,如果题目要求每两个单词之间至少要保留一个-,那么让快指针每次从’-‘走到有意义的字符时提醒慢指针添加一个’-‘即可
1
2
3
4
5
6
7
8
9
10
11
12
13// 版本二
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
翻转句子中的单词
He is a handsome boy.
- 首先完全翻转 yob emosdnah a si eH.
- 然后将每个单词又翻转回来即可 boy handsome a is He.
K!M!P!
首先我想到的是快慢指针法
快指针和慢指针一开始都指向开头,快指针先顺次遍历,直到匹配不上
这个时候将慢指针移到快指针不匹配的地方,快指针开始从头匹配直到有一天快指针终于匹配上,慢指针在的位置就是要找的位置。
然而这样是错的!
问题的关键在于如果快指针所经历的后缀真子串(我发明的叫法)包含了模式串的前缀真子串,这时候慢指针一跳就把这部分忽略了:
当到达下面的状态时
慢指针直接跳到b从b开始匹配,就把模式串的前缀真子串给略过了
所以我们要用前缀表
使用前缀表,在不匹配的时候在模式串中回退,这种回退必须是考虑到模式串子前缀的,而前缀表可以实现这一点,当模式串匹配失败时,可以检查前缀表考察前面一个子串有没有公共前后缀,如果有的话,那么应该从公共前缀处继续尝试匹配或回退直到退无可退为止(j==0)
详解:实现strstr()
构造前缀表本身就很难
构造前缀表也要用到已经构造好的前缀表,我们用j表示已经匹配到的前缀,当匹配不上时也是一遍回退一遍继续尝试
复杂度
KMP可以将复杂度从暴力算法的O(MN)变成O(M+N)
完整代码
1 | class Solution { |
检测链表环并找到环入口
在一个单链表中可能存在一个环,找到这个环的入口
检测一个环是很简单的(虽然我还是没想出来),只要一个慢指针一个快指针,慢指针每次走一格,快指针每次走两格,只要有换,快慢指针肯定会碰上
环有了,入口在哪里?
首先要明确一点,fast和slow相遇时,slow还没有在环里走环一圈
可以这样理解:
如果fast和slow同时从环入口出发,那么他们下次相遇也是环入口,这个时候fast走了两圈,slow走了一圈
现在的情况是因为有了X,因此slow进来的时候,fast已经在环的某个位置了,那fast就不用超过slow一圈就能追上了,所以slow是走不满一圈的
那么,相遇时,slow走的距离就是
这个式子意味着如果我们让一个指针从头出发,一个指针从相遇处出发,那么他们相遇的地方就是入口节点
1 | public class Solution { |
三数之和为0
给定一个数组,找到其三数之和为0的所有组合,不能重复
排序
一个i从左向右遍历数组,压缩区间,left right分别在区间的两边,按双指针向中间靠拢,不断寻找令nums[left]+nums[right]==-nums[i]
的组合即可
去重
i的去重很简单,如果遍历到和上一个一样的i直接继续下一个i即可
left和right应该在找到一个三元组后去重,参考:-3 -1 -1 4 4
注意应该直接在针对这个三元组的结果上去重,而不是傻乎乎的先加减left和right之后再去重
分析这两种写法
1 | ans.add(arr); |
1 | ans.add(arr); |
对于输入[-2,0,1,1,2]
第一种写法在找到-2,0,2后直接++–,然后去重,导致-2,1,1被当做重复去掉了
第二种写法不会有这种情况
第一种写法(也就是我的想法)以为可以直接去取重复序列的最后一个值,殊不知这样会挤压另一个指针取到这个重复序列值的可能
第二种方法则总是取重复序列的第一个值,然后去重,这样不会挤压另一个指针的生存空间
想要把第一种方法改对?只需要将nums[left+1]和nums[right-1]改成nums[left-1]和nums[right+1]就能够总是取重复序列的第一个值了
1 | ans.add(arr); |
翻转单链表
(面试百度的时候问到了这个)
最基本的双指针用法,但是要把代码写得优雅精巧是一件很困难的事
1 | class Solution{ |