双指针(尺取法)
双指针顾名思义,同时使用两个指针维护或者统计一些区间信息的。特别是在数组、链表等数据结构中,双指针的算法能大大减少我们的编码速度以及时间复杂度。
双指针大体的应用分为以下三个大类:
碰撞指针、 滑动窗口 、 快慢指针
碰撞指针
两个指针从头尾向中间移动。
模板:
while(l<r) {
//具体问题逻辑
//至少移动一个指针
}
Leetcode 两数之和 II - 输入有序数组
题意:在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int l=0,r=numbers.size()-1;
while(l<r) {
int sum=numbers[l]+numbers[r];
if(sum==target) return {l+1,r+1};
else if(sum>target) r--;
else l++;
}
return {-1,-1};
}
};
Leetcode 633. 平方数之和
题意:给定一个非负整数
c
c
c ,你要判断是否存在两个整数
a
a
a 和
b
b
b,使得
a
2
+
b
2
=
c
a^2 + b^2 = c
a2+b2=c 。
class Solution {
public:
bool judgeSquareSum(int c) {
long long r=sqrt(c);
long long l=0,cc;
while(l<=r) {
cc=l*l+r*r;
if(cc==c) return true;
else if(cc>c) r--;
else l++;
}
return false;
}
};
Leetcode 680. 验证回文串 II
题意:给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
class Solution {
public:
bool check(int l,int r,string s){
while(l<r) {
if(s[l]!=s[r]) return false;
++l;
--r;
}
return true;
}
bool validPalindrome(string s) {
int l=0,r=s.size()-1,cnt=0;
while(l<r) {
if(s[l]==s[r]) {
++l;
--r;
}
else return check(l,r-1,s)||check(l+1,r,s);
}
return true;
}
};
滑动窗口
两个指针从头同向移动。
模板:
for(int l=1,r=1;l<=n;++l){ //遍历左指针
while(r<=n && 某条件) { //右指针向右扩展
//具体问题逻辑
//包含进右指针元素
++r;
}
//循环退出后,统计/维护的区间是[l,r)
if(某条件) break;
//具体问题逻辑...
//剔除左指针元素...
}
维护区间和或积
POJ-3061 Subsequence
题意:给定长度为
N
(
10
<
N
<
1
0
5
)
N(10 < N <10^5)
N(10<N<105) 的正整数数列
a
0
,
a
1
,
…
…
,
a
n
−
1
a_0,a_1,……,a_{n-1}
a0,a1,……,an−1 以及正整数
S
(
0
<
S
<
1
0
8
)
S(0 < S < 10^8)
S(0<S<108)。 求出总合不小于S的连续 子序列长度的最小值。如果解不存在,则输出0。
参考代码:
#include <iostream>
using namespace std;
#define int long long
const int N=1e5+5;
int n,s,a[N],T;
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld%lld",&n,&s);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
int sum=0,mi=n+1;
for(int l=1,r=1;l<=n;++l){
while(r<=n && sum<s) {
sum+=a[r];
r++;
}
//sum=[l,r)之间的值的和
if(sum<s) break;
mi=min(mi,r-l);
sum-=a[l]; //尝试移动左指针一小步
}
if(mi==n+1) printf("0\n");
else printf("%lld\n",mi);
}
return 0;
}
Leetcode 76. 最小覆盖子串
题意:给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
参考代码:
class Solution {
public:
unordered_map<char,int> mp;
string minWindow(string s, string t) {
for(int i=0;i<t.size();++i) mp[t[i]]++;
int cnt=mp.size();
int ansi=-1,ansj=-1;
for(int l=0,r=0;l<s.size();++l) {
while(r<s.size() && cnt) {
if(mp.find(s[r])!=mp.end()){
mp[s[r]]--;
if(mp[s[r]]==0) cnt--;
}
r++;
}
if(cnt) break;
if(ansi==-1 || r-l<(ansj-ansi)) {
ansi=l;
ansj=r;
}
if(mp.find(s[l])!=mp.end()) {
mp[s[l]]++;
if(mp[s[l]]==1) cnt++;
}
}
if(ansi==-1) return "";
return s.substr(ansi,ansj-ansi);
}
};
快慢指针
Leetcode 142. 环形链表 II
题意:给定一个链表,如果有环路,找出环路的开始点。
题解:给定两个指针,分别为fast和slow,从起始位置开始,fast每次前进两步,slow每次前进一步。如果fast为空或者next为空,则说明没有环路。否则fast会一直走下去,说明有环路,两者一定会相遇。当fast和slow相遇时,再将fast重新移动到head,并让fast和slow每次都前进一步,当两者再次相遇时,相遇节点即为环路开始点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast=head;
ListNode *slow=head;
do{
if(fast==NULL) return fast;
fast=fast->next;
if(fast==NULL) return fast;
fast=fast->next;
if(fast==NULL) return fast;
slow=slow->next;
}while(fast!=slow);
fast=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
};