AcWing 799. 最长连续不重复子序列

208 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

题目

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 n。

第二行包含 n 个整数(均在 0∼10^5 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围

1 ≤ n ≤ 10^5

输入样例:

5
1 2 3 2 5

输出样例:

3

思路

双指针算法

38626_0aed0a9e9b-1.png

创建一个 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;
}