目录
一、什么是双指针?什么是滑动窗口?
双指针是我们在解决数组的相关题目时常用的方法,定义两个指针,以多种方式去处理一个数组。
常用的有:
1.快慢指针:通过设置两指针的初始位置,移动步长,可实现两指针一快一慢遍历数组。
2.头尾指针:一个定义在头,一个在尾,可以两个方向同时处理。
3.滑动窗口:两个指针一前一后形成一个窗口,不满足条件时前面的不停扩大窗口,满足时后面指针前进,缩小窗口,由可行解寻找最优解。
二、双指针实操
1.力扣27
本题要求原地移除数组中的某些元素,不能使用额外空间,且元素顺序可以改变,无需考虑新数组后面的元素。
思路:遇到val交换到原数组后面,即采用首尾指针。
class Solution {
public:
int removeElement(vector<int>& nums, int val)
{
int n = nums.size();
int i = 0;
int j = n-1;
int ans=0;
while(i<=j)
{
if(nums[j]==val)
{
--j;
++ans;
continue;
}
if(nums[i]==val)
{
swap(nums[i],nums[j]);
--j;
++ans;
}
++i;
}
return n-ans;
}
};
相关题目:力扣977,11
2.141环形链表
判断是否成环,如果成环不断向下循环不会有nullptr,否则会找到nullptr。
思路:利用快慢指针,如是环路,终会相遇,不是环路就会找到nullptr;
class Solution {
public:
bool hasCycle(ListNode *head)
{
if(head==nullptr||head->next==nullptr)return false;
ListNode*fast = head->next;
ListNode*slow = head;
while(fast->next != nullptr&&fast->next->next!=nullptr)
{
fast=fast->next->next;
slow = slow->next;
if(fast == slow)return true;
}
return false;
}
};
相关题目 力扣 相交链表
三.滑窗实操
(1)长度最小的子数组
暴力思路:循环遍历,每次满足条件记录长度,然后下次循环,但是其中存在冗余的操作,本题要使和为target,没必要每次从头开始求和,利用之前求过的和,下次循环就是把头去掉,加上尾部。
思路:
要求是连续的子数组,和大于等于target且长度最小,那么窗口的限制就是和大于等于target
右侧指针先扩大窗口,满足条件,左侧收缩窗口直至不满足。
int minSubArrayLen(int target, vector<int>& nums)
{
int n= nums.size();
int i=0;
int j=0;
int sum =0;
int size = INT_MAX;
while(j<=n-1)
{
sum += nums[j];
while(sum>=target)
{
if(j-i+1<size)size=j-i+1;
sum -= nums[i++];
}
++j;
}
if(size==INT_MAX)size=0;
return size;
}
(2)904水果成篮
思路:
本题提供两个篮子,没个篮子只能装一种,数量不限,那么窗口就是2即为篮子数,
向右扩大窗口,一旦满足条件,超过了篮子数量,左侧指针缩减窗口。
本题有两个因素,树的种类和果子个数,因此采用哈希表存储
int totalFruit(vector<int>& fruits)
{
unordered_map<int,int>un_map;
int n = fruits.size();
int i = 0;
int j=0;
int size = INT_MIN;
for(;j<n;j++)
{
un_map[fruits[j]]++;
while(un_map.size()>2)
{
int kind = fruits[i++];
if(--un_map[kind]==0)un_map.erase(kind);
}
size = max(size,j-i+1);
}
return size;
}
(3)76最小覆盖字串
思路:
找出s中涵盖t的最小子串,窗口就是t,当涵盖所有字符时满足条件,左侧指针缩减窗口寻找最优解,本题涉及字母的类型以及个数,所以依旧选择哈希表存储,事先存储t,另一个存s,当t哈希表所有元素数目小于等于s中的 则满足条件。
class Solution {
public:
unordered_map<char,int>map_t;
unordered_map<char,int>map_s;
bool check()
{
for(const auto &it:map_t)
{
if(map_s[it.first]<it.second)
{
return false;
}
}
return true;
}
string minWindow(string s, string t)
{
int b=0;
int e=0;
int lengh=INT_MAX;
int ansl=0;
for(int i=0;i<t.size();i++)
{
++map_t[t[i]];
}
for(;e<s.size();e++)
{
if(map_t.find(s[e])!=map_t.end())
++map_s[s[e]];
while(check())
{
if(e-b+1<lengh)
{
lengh =e-b+1;
ansl=b;
}
if(map_t.find(s[b])!=map_t.end())
--map_s[s[b]];
++b;
}
}
return lengh==INT_MAX?string():s.substr(ansl,lengh);
}
};
(4)三数之和
思路:暴力解法,三层for循环,判断是否和为0,但是需要去重,我们需要现将数组排序,然后跳过重复的位置,我们使用一层循环确定一个数,剩下两个采取双指针的方式,这样三层变成两层循环。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
sort(nums.begin(),nums.end());
vector<vector<int>>ans;
for(int i=0;i<nums.size();++i)
{
if(i>0&&nums[i]==nums[i-1])
{
continue;
}
int target=-nums[i];
int right=nums.size()-1;
for(int left=i+1;left<nums.size();++left)
{
if(left>i+1&&nums[left]==nums[left-1])
{
continue;
}
while(left<right&&nums[left]+nums[right]>target)
{
--right;
}
if(left==right)
{
break;
}
if(nums[left]+nums[right]==target)
{
ans.push_back({nums[i],nums[left],nums[right]});
}
}
}
return ans;
}
};
总结
双指针一般分两种,同向和逆向,逆向多见于数组,同向多见于链表;
滑窗基本思路,找到窗口,选择合适的存储,先扩大窗口,满足条件,在缩小窗口由可行解找最优。
滑窗适合判断数组的连续子数组是否满足一定的条件,寻找最优解。