1.分析
双指针技巧再分为两类,一类是**「快慢指针」,一类是「左右指针」**。
前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环;后者主要解决数组(或者字符串)中的问题,比如二分查找。
2.快慢指针
2.1 判断单向链表是否有环(Leetcode141)
两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。
class Solution {
public:
bool hasCycle(ListNode *head)
{
ListNode* fast=head;
ListNode* slow=head;
//若无环一定是快指针先遇到NULL
while((fast!=NULL)&&(fast->next!=NULL))
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
};
2.2 返回单向链表环的起始位置
2.3 寻找链表的中点
LeetCode876. Middle of the Linked List
类似上面的思路,我们还可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。当链表的长度是奇数时,slow恰巧停在中点位置;如果长度是偶数,slow最终的位置是中间偏右。
class Solution {
public:
ListNode* middleNode(ListNode* head)
{
ListNode* slow=head;
ListNode* fast=head;
while((fast->next!=NULL)&&(fast->next->next!=NULL))
{
slow=slow->next;
fast=fast->next->next;
}
if((fast->next!=NULL)&&(fast->next->next==NULL))
return slow->next;
return slow;
}
};
2.4 寻找链表的倒数第n个元素
LeetCode19. Remove Nth Node From End of List
我们的思路还是使用快慢指针,让快指针先走n步,然后快慢指针开始同速前进。这样当快指针走到链表末尾null时,慢指针所在的位置就是倒数第n个链表节点(n不会超过链表长度)。
【注意】判断n步后,fast是否到头!!!若到头则删除第一个结点即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* fast=head;
ListNode* slow=head;
while(n--)
{
fast=fast->next;
}
ListNode *save=NULL;
//如果fast==NULL,说明要删除的为头结点
if(fast==NULL)
return head->next;
while(fast!=NULL)
{
fast=fast->next;
save=slow;
slow=slow->next;
}
//此时slow指向需删除点
save->next=slow->next;
return head;
}
};
3.左右指针
3.1 二分查找
代码中left + (right - left) / 2就和(left + right) / 2的结果相同
class Solution {
public:
int search(vector<int>& nums, int target)
{
int right=nums.size()-1;
int left=0;
while(left<=right)
{
int mid=left+(right-left)/2;
if(nums[mid]==target)
{
return mid;
}
else if(nums[mid]<target)
{
left=mid+1;
}
else if(nums[mid]>target)
{
right=mid-1;
}
}
return -1;
}
};
3.2 寻找合适的两数之和【LeetCode167】
只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节left和right可以调整sum的大小:
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left=0;
int right=numbers.size()-1;
while(numbers[left]+numbers[right]!=target)
{
if(numbers[left]+numbers[right]<target)
{
left++;
}
else if((numbers[left]+numbers[right]>target))
{
right--;
}
}
vector<int> ans;
ans.push_back(left+1);
ans.push_back(right+1);
return ans;
}
};
3.3 滑动窗口
1、我们在字符串S中使用双指针中的左右指针技巧,初始化left = right = 0,把索引左闭右开区间[left, right)称为一个「窗口」。
2、我们先不断地增加right指针扩大窗口[left, right),直到窗口中的字符串符合要求(包含了T中的所有字符)。
3、此时,我们停止增加right,转而不断增加left指针缩小窗口[left, right),直到窗口中的字符串不再符合要求(不包含T中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到right到达字符串S的尽头。
【注意】最小窗口值是在收缩left指针时保存的,此时right值一定是符合要求留的最小可能右值,left在跳出while循环前最后一次也更新为最小值
class Solution {
public:
string minWindow(string s, string t)
{
//需要包含的字符集
map<char,int> mp;
//当前已扫描到的字符集
map<char,int>win;
for(int i=0;i<t.size();i++)
{
mp[t[i]]++;
}
int left=0;
int right=0;
int start=0;int len=INT_MAX;
int keep=0;//成功包含的字符集数
//先扩大右指针
while(right<s.size())
{
char save=s[right];
if(mp.find(save)!=mp.end())
{
win[save]++;
if(win[save]==mp[save])
keep++;
}
//包含要求的字符串,开始收缩左侧边界
while(keep==mp.size())
{
if(right-left<len)
{
start=left;
len=right-left;
}
char save2=s[left];
if(mp.find(save2)!=mp.end())
{
if(win[save2]==mp[save2])
keep--;
win[save2]--;
}
left++;
}
right++;
}
if(len==INT_MAX)
return "";
return s.substr(start,len+1);
}
};