给定一个长度为 n 的非负整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
一般做法:
- 对于 i ∈ [ 0 , n − 1 ] i\in\lbrack0,\;n\;-\;1\rbrack i∈[0,n−1]) ,找到整数序列中以 i i i为区间右端点的最长的不包含重复元素的连续区间,得到其长度 k i k_i ki。
- k = m a x { k i ∣ i ∈ [ 0 , n − 1 ] } k\;=\;max\{k_{i\;}\vert\;i\in\lbrack0,\;n-1\rbrack\;\} k=max{ki∣i∈[0,n−1]}
简化型代码
int k = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < i; j++){
if(!check(j, i)){ //check(j, i)是检查区间[j, i]内是否存在重复元素,如果存在,则返回true, 否则,返回false。
k = max(k, i - j + 1); //k为最大长度
break;
}
}
}
改良版
上述代码实现时出现的问题:
当
i
i
i 改变了,
j
j
j 的值要从0开始遍历,这一点大可不必。观察下图:
假如,此时区间
[
j
,
i
]
\lbrack j,i\rbrack
[j,i]之间出现了重复元素
a
[
m
]
=
a
[
n
]
a\lbrack m\rbrack=a\lbrack n\rbrack
a[m]=a[n],那么下次当
i
i
i 移动后,
j
j
j 其实没有必要从0开始,而至少是从
m
m
m 之后,
i
i
i 之前的某个位置开始。
解决思路
- 在计算
k
i
k_i
ki 时,在任意时刻,
[
j
,
i
]
\lbrack j,\;i\rbrack
[j,i] 区间只存在两种情况:
c h e c k ( j , i ) = { t r u e 区 间 [ j , i ] 包 含 重 复 元 素 f a l s e 区 间 [ j , i ] 不 包 含 重 复 元 素 check(j,i)=\left\{\begin{array}{l}true\;\;\;\;\;\mathrm{区间}\lbrack j,i\rbrack\mathrm{包含重复元素}\\false\;\;\;\mathrm{区间}\lbrack j,i\rbrack\mathrm{不包含重复元素}\end{array}\right. check(j,i)={true区间[j,i]包含重复元素false区间[j,i]不包含重复元素
(1)当 c h e c k ( j , i ) = f l a s e check(j,i)=flase check(j,i)=flase 时, k i = i − j + 1 k_i=i-j+1 ki=i−j+1 ;
(2)当 c h e c k ( j , i ) = t r u e check(j,i)=true check(j,i)=true 时,区间 [ j , i ] \lbrack j,\;i\rbrack [j,i] 包含重复元素,假定重复元素为 a [ m ] = a [ n ] a\lbrack m\rbrack=a\lbrack n\rbrack a[m]=a[n],则慢慢移动 j j j,直到 c h e c k ( j , i ) = f a l s e check(j,i)=false check(j,i)=false 为止,此时 j = m j=m j=m, k i = i − j + 1 k_i=i-j+1 ki=i−j+1。
- 移动 i i i , j j j 从 m m m 开始,重复第一步骤
- k = m a x { k i ∣ i ∈ [ 0 , n − 1 ] } k\;=\;max\{k_{i\;}\vert\;i\in\lbrack0,\;n-1\rbrack\;\} k=max{ki∣i∈[0,n−1]}
改良版简化代码
int k = 0;
for(int i = 0, j = 0; i < n; i++){
while(j < i && check(j,i))
j++;
k = max{k, i - j + 1};
}
具体实现
在整个具体实现过程中,关键在于 c h e c k ( j , i ) check(j\;,\;i) check(j,i) 如何实现:
一般做法(笨办法)
//检查数组a[N]中是否存在重复元素。
bool check(int j, int i){
for(int m = j; m < i; m++){
for (int n = m; n < i; n++)
if(a[n] == a[m])
return true;
}
return false;
}
巧妙方法
要确定一个数组里是否存在重复元素,可以考虑先统计数组的每个元素出现的次数,如果有某个元素出现的次数大于1,说明该数组中存在重复元素。
bool check(int i, int j, int ){
for(int i = 0;i < n; i++){
s[a[i]]++;
if(s[a[i]] > 1)
return true;
}
return false;
}
完整实现代码
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], s[N];
int n;
int main(){
scanf("%d", &n);
int k = 0;
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0 , j = 0; i < n; i++){
s[a[i]]++;
while(s[a[i]] > 1){
s[a[j]]--;
j++;
}
k = max(k, i - j + 1);
}
printf("%d", k);
return 0;
}