双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多个数组的多个指针
若两个指针指向同一数组,遍历方向相同且不会相交,则也称为滑动窗口(两个指针包围的区域即为当前的窗口),经常用于区间搜索;
若两个指针指向同一数组,但是遍历方向相反,则可以用来进行搜索,待搜索的数组往往是排好序的。
167.两数之和
通过一边遍历解决在一个增序的整数数组里找到两个数,使他们的和为给定值。已知有且只有一对解。 使用遍历方向相反的指针
vector<int> twoSum(vector<int>& numbers, int target) {
int l=0, r=numbers.size()-1, sum;
while(l<r){ //循环条件
sum = numbers[l] + numbers[r];
if(sum == target) break;
if(sum < target) l++;
else r--;
}
return vector<int>{l+1,r+1}; //vector返回值写法
}
88.合并两个有序数组
给定两个有序数组,把两个数组合并为一个
两个数组长度为m,n;其中一个数组的长度被延长至m+n,多出的n用0补位,不需要开辟额外空间
题解:这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即nums1的m-1位和nums2的n-1位。
每次将较大的数字复制到nums1的后边,然后向前移动一位。因为我们也要定位这个移动的下标,所以还需要第三个指针
void merge(vector<int>& nums1, int m, vector<int>&nums2, int n) {
int pos = m-- + n-- -1;
while(m >= 0 && n >= 0 ){
if(nums1[m] > nums2[n]) nums1[pos--] = nums1[m--];
else nums1[pos--] = nums2[n--];
}
while(n>=0){
nums1[pos--]=nums2[n--];
}
}
142.链表是否有环
给定一个链表,如果有环路,找出环路的开始点*,如果没有环路返回一个空指针
题解:对于链表找环路问题,有一个通用的解法---快慢指针,给定两个指针,分别命名为slow和
fast,起始位置在链表的开头。每次fast前进两步,slow前进一步,如果fast可以走到尽头,那么
说明没有环路;如果fast可以无线循环下去,那么说明一定有环路,且一定存在一个时刻slow和fast
相遇。当slow和fast第一次相遇时,我们将fast重新移动到链表开头,并让slow和fast每次都前进
一步。当slow和fast第二次相遇时,相遇的节点即为环路的开始点。
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr) {} //链表初始化方法
};
哈希表 简单粗暴 判断链表是否有环 时间空间复杂度 O(N)
ListNode *detectCycle(ListNode *head){
unordered_set<ListNode*> visited; //用set可以,unordered_set是哈希set,无序的速度快
while(head != nullptr){
if(visited.count(head)){
return head;
}
visited.insert(head);
head = head->next;
}
return nullptr;
}
快慢指针方法 时间复杂度O(N) 空间复杂度O(1)
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
//判断是否存在环路
do{
if(!fast || !fast->next) return nullptr;
fast = fast->next->next;
slow = slow->next;
}while(fast != slow);
//如果存在,查找环路节点
fast = head;
while(fast != slow){
slow=slow->next;
fast=fast->next;
}
return fast;
}
76.最小覆盖子串(hard)
题目描述:给定两个字符串S和T,求S中包含T所有字符的最短连续子字符串的长度,同时要求时间复杂度不得超过O(n)
输入输出样例:输入是两个字符串S和T,输出是一个S字符串的子串。
Input:S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"
在这个样例中,S中同时包含一个A、一个B、一个C的最短子字符串是 "BANC"
题解:本题使用滑动窗口求解,即两个指针l和r都是从最左端向最右端移动,且l的位置一定在r的左边或重合。注意本题虽然在for循环里出现一个while循环,
但是因为while循环负责移动l指针,且l只会从左到右移动一次,因此总时间复杂度仍然是O(n).本题使用长度为128的数组来映射字符,也可以用哈希表来代替;
其中chars表示目前每个字符缺少的数量,flag表示每个字符是否在T中存在。
string minWindow(stirng S, string T){
vector<int> chars(128, 0);
vector<bool> flag(128, false);
//先统计T中的字符情况
for(int i=0; i<T.size(); ++i){
flag[T[i]]= true;
++chars[T[i]];
}
//移动滑动窗口,不断更改统计数据
int cnt=0, l=0, min_l=0, min_size=S.size()+1;
for(int r=0; r<S.size(); ++r){
if(flag[S[r]]){
if(--chars[S[r]] >= 0){ //因为T中可能有重复的 所用>= 而不是==
++cnt;
}
//若目前滑动窗口已包含T中全部字符,
//则尝试将l右移,在不影响结果的情况下获得最短子字符串
while(cnt == T.size()){
if(r-l +1 < min_size){
min_l=l;
min_size=r-l+1;
}
if(flag[S[l]] && ++chars[S[l]]>0){
--cnt;
}
++l;
}
}
}
return min_size > S.size()? "":S.substr(min_l,min_size);
}
类型:
- 贪心算法
- 双指针
- 二分查找
- 排序算法
- 二维数组的搜索
- 动态规划
- 数据结构-stl
- 字符串比较、匹配
- 链表
- 二叉树