双指针算法有时候也叫尺取法或者滑动窗⼝,是⼀种优化暴⼒枚举策略的⼿段:
解法1:暴力枚举:2层for循环
借助哈希表判断枚举的子数组中,所有的元素都不相同
解法2:利用单调性,使用同向双指针来优化
在一个数组中,选择一个最长连续的区域,所有的元素都不相同
当我们「暴⼒枚举」的过程中,固定⼀个起点位置left,然后right之后向后遍历时。当right第
⼀次扫描到⼀个位置,使[left,right]
这个区间「出现重复字符」,此时我们会发现:
[left,right]
区间的信息,并且left+1为起点的最优解⼀定不会⽐left为起点的好。left = 1;
right = 1;
unordered_map mp;
mp[a[right]] ++;
mp[a[right]] > 1;
mp[a[left]] --;
ret = max(ret, right-left+1);
#include
using namespace std;
const int N = 1e6 + 10;
int n;
int a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T; cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
//初始化
int left = 1, right = 1, ret = 0;
unordered_map mp;
while (right <= n)
{
//进窗口
mp[a[right]]++;
//判断
while (mp[a[right]] > 1)
{
mp[a[left]]--;
left++;
}
//窗口合法,更新结果
ret = max(ret, right-left+1);
right++;
}
cout << ret << endl;
}
return 0;
}
当我们「暴⼒枚举」的过程中,固定⼀个起点位置left ,然后right之后向后遍历时。当[left,right]
第⼀次扫描到⼀个位置,使[left,right]
这个区间已经「包含了所有的数时」,此时我们会发现:
[left,right]
区间的信息,能够快速得出[left,right]
区间「是否合法」,⽽且最「优右端点」铁定不在right左边。left = 1;
right = 1;
int mp[];
kind = 0;
mp[a[right]]++;
//0->1
kind++
m == kind;
mp[a[left]]--;
//1->0
kind--;
ret = min(ret, right-left+1);
begin = left;
#include
using namespace std;
const int N = 1e6 + 10, M = 2e3 + 10;
int n, m;
int a[N];
int kind;
int mp[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
int left = 1, right = 1;
int ret = n, begin = 1;
while (right <= n)
{
//进窗口
if (mp[a[right]]++ == 0) kind++;
//判断
while (kind == m)
{
//更新结果
int len = right-left+1;
if (len < ret)
{
ret = len;
begin = left;
}
//出窗口
if (mp[a[left]]-- == 1) kind--;
left++;
}
right++;
}
cout << begin << " " << begin+ret-1 << endl;
return 0;
}
当我们「暴⼒枚举」的过程中,固定⼀个起点位置left ,然后right之后向后遍历时。当right第
⼀次扫描到⼀个位置,使[left, right]
这个区间已经「包含了所有的⼩写字⺟时」,此时我们会发现:
[left, right]
区间的信息,能够快速得出[left+1, right]
区间「是否合法」,⽽且最「优右端点」铁定不在right左边。#include
using namespace std;
string s;
int mp[26];
int kind;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> s;
int n = s.size();
int ret = n;
for (int left = 0, right = 0; right < n; right++)
{
//进窗口
if (mp[s[right] - 'a']++ == 0) kind++;
//判断
while (kind == 26)
{
//更新结果
ret = min(ret, right-left+1);
//出窗口
if (mp[s[left] - 'a']-- == 1) kind--;
left++;
}
}
cout << ret << endl;
return 0;
}
分成两段分析
[left, right]
以及[right, left]
设left, right
区间内的距离是k
right, left
的距离是sum-k
其中sum是整个圆圈的长度
当我们「暴⼒枚举」的过程中,固定⼀个起点位置left ,然后right之后向后遍历时,记k为[left, right]
之间的距离。当right第⼀次扫描到k x 2 >= sum时,此时我们会发现:
[left, right]
区间的信息,right回退也不是最优解。[left, right]
之间可能是最优解,⽤k 更新结果;[right, left]
之间可能是最优解,⽤sum - k更新结果。#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n;
LL a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
LL sum = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum += a[i];
}
int left = 1, right = 1;
LL k = 0;
LL ret = 0;
while (right <= n)
{
k += a[right];
while (2 * k >= sum)
{
//用sum-k
ret = max(ret, sum - k);
k -= a[left++];
}
//用k
ret = max(ret, k);
right++;
}
cout << ret << endl;
return 0;
}