持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
题目
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼10^5 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1 ≤ n ≤ 10^5
输入样例:
5
1 2 3 2 5
输出样例:
3
思路
双指针算法
创建一个 s 数组,当作map使用,记录每一个数字出现的次数,s数组的下标即为数字的大小,s[i] 为出现的个数
遍历数组a中的每一个元素a[i], 对于每一个i,找到 j 使得双指针[j, i]维护的是以a[i]结尾的最长连续不重复子序列,长度为i - j + 1, 将这一长度与 res 的较大者更新给 res。
对于每一个i,如何确定j的位置:由于[j, i - 1]是前一步得到的最长连续不重复子序列,所以如果[j, i]中有重复元素,一定是a[i],因此右移j直到a[i]不重复为止(由于[j, i - 1]已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j具有“单调性”、本题可用双指针降低复杂度)。
用数组s记录子序列a[j ~ i]中各元素出现次数,遍历过程中对于每一个i有四步操作:cin元素a[i] -> 将a[i]出现次数s[a[i]]加1 -> 若a[i]重复则右移j(s[a[j]]要减1) -> 确定j及更新当前长度i - j + 1给r。 注意细节:当a[i]重复时,先把a[j]次数减1,再右移j。
while (j < i && s[q[i]] > 1) s[q[j ++ ]] -- ;
如果j < i 并且 当前 i指针所指向的数字出现的次数大于 1 ,j 指针就加一,并且 j 每一次移动都会减掉 j 指针 所指向的数字的出现次数
因为每一次获取的都是最长连续不重复子序列,如果数组中含有重复元素,则一定是 a[i],那么我们就要去掉重复的数字,所以j ++,直到 当前的子序列中没有重复元素为止
双指针模板
for (int i = 0, j = 0; i < n; i ++ ){
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
ac代码
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int q[N], s[N];
int main(){
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i];
int res = 0;
for (int i = 0, j = 0; i < n; i ++ ){
s[q[i]] ++ ;//记录每个数组出现的个数
while (j < i && s[q[i]] > 1) s[q[j ++ ]] -- ;
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}