双指针
在归并排序和快速排序中均有使用,常用的方法:
- 分别指向2个队列
- 分别指向1个队列的首尾
其作用是显而易见的,普通的方法可能要遍历2个数组,即O(n^2),但双指针算法总是小于2n,即O(n)的复杂度
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
**输入:** s = "abcabcbb"
**输出:** 3
**解释:** 因为无重复字符的最长子串是 `"abc"`,所以其长度为 3。
这题写了很久,第一次做很久都没能通过。我的第一反应的想法就是双循环遍历,但即使是最简单的方法也老是出问题。
双循环(me):
int lengthOfLongestSubstring(string s) {
/* 在字符串不为空的情况下,答案至少是为1的,下面没写好,所以为了图方便这里直接这么做了 */
int max = 1;
if(s.size() == 0){
max = 0;
}
for(int i = 0; i < s.size(); i++){
for(int j = i + 1; j < s.size(); j++){
for(int k = i; k < j; k++){
/**/
if(s[k] == s[j]){
if(j - i > max){
max = j - i;
}
/* 希望退出2层循环,但不想用goto,自己想的小trick */
j = s.size() + 10;
break; }
}
/* 针对j一直走到头的情况 */
if(j == s.size()-1){
if(j - i + 1> max){
max = j - i + 1;
}
}
}
}
return max;
}
显然,这个方法时间复杂度是O(n2),如果算上中间的查重步骤,复杂度可能到了O(n3)
下面用双指针来实现,用一个数组b来判重,
int lengthOfLongestSubstring(string s) {
int b[128] = {0};
int max = 0;
/**
* i在左边,j在右边
* j向前进的过程中每当遇到一个字符,就会去b这个字典里面对其出现的次数+1
* 而当b中对应的大小大于1时,说明其出现了不止一次,则要i从左向右边移动,去掉这个重复的字符
* 当j不断向右移动的过程中,i也总是向右移动的(j向右移动,i能向左移的话,说明前一个i的位置是错误的)
* 这样的单调性简化了算法
* 如何说明这样的过程是没有遗漏的呢?
* */
for(int i = 0, j = 0; j < s.size(); j++) {
b[s[j]]++;
while(b[s[j]] > 1) {
b[s[i]]--;
i++;
}
max = std::max(max, j - i + 1);
}
return max;
}
上一种做法耗时264ms,下一种做法耗时4ms,无论是代码量还是效率都完全的碾压了。
快慢指针
![[Pasted image 20231214173255.png]]
简而言之就是一个指针快,一个指针慢,用于确定一个区间范围
for(i = 0, j = 0; j < nums.size(); j ++){
if(nums[j] != val){
nums[i ++] = nums[j];
}
}
return i;