题目链接:B. Uniqueness
题意:n (1 - 2000) 个数,只删除一个子串使得数组中没有重复元素,求删除的子串长度。
思路(参考博客cf1208B B. Uniqueness):
逆向考虑问题,要删除的最少区间长度对应最多能保留多少个数,又因为区间要是连续的,所以只能删除左端区间,或中区间,或右边区间。利用两个map,第一个存从左往右能保留的每个元素的下标,并用一个数记录最终下标;第二个map从右往左扫描,用于记录当前元素的右半部分能保留多少数,如果出现与其右部分的重复元素,则计算其中间要删除的元素个数,并判断是否要更新答案,否则进行标记,若当前右半部分的元素与一开始左半部分的元素,则模拟对左半部分元素删除操作,同时更新删除元素个数找出最佳答案,具体情况看代码和注释。
暴力的话直接枚举所有可能的长度,二分的话就是二分可能的长度,就是再暴力的基础上多了一点优化。
原博主代码:
#include <bits/stdc++.h>
using namespace std;
map<int, int > book1, book2;
int main() {
int n;
int a[2005];
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int ans = 99999;
// p记录左半部分能保留数字的下标(即个数), q记录右半部分能保留到的下标,q-p则为要删除的序列的大小
int p = 1, q = n;
// 从左往右扫描,记录从左往右能保留多少个数,即遇到重复数字停止扫描
while (p <= n) {
if (book1[a[p]]) {
break;
} else {
book1[a[p]] = p;
}
p++;
}
// 由于扫描的时候是判断下一位,所以p要减一才是实际最后那个不重复元素的下标(个数)
p--;
ans = n - p; // 此时要删除的元素为元素总个数减去前左半部分要保留的数字
// 从右往左扫描
while (q >= 1) {
if (book2[a[q]]) { // 如果q右半部分的数字在左半部分没出现过,但q右半部分的数字出现重复,则进行判断后结束扫描
if (ans > q - p) ans = q - p; // 如果此时要删除的元素个数比之前少,则更新答案
break;
} else { // 否则进行标记该元素出现过
book2[a[q]] = 1;
}
if (book1[a[q]] && p >= book1[a[q]]) { // 如果右半部分的最左端的元素与一开始左半部分要保留的数字重复,
//则模拟左半部分元素删除操作,同时判断是否要更新答案和更新左半部分要保留的数字下标
if (ans > q - p) ans = q - p;
p = book1[a[q]] - 1;
}
q--;
}
printf("%d\n", ans);
return 0;
}
我就贴暴力的代码了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2010;
int n;
int a[N];
map<int, int> ma;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int ans = n - 1;
for (int i = 1; i <= n; i++)
{
ma.clear();
bool ok = true;
for (int j = 1; j < i; j++)
{
ma[a[j]]++;
if (ma[a[j]] == 2)
{
ok = false;
break;
}
}
if (!ok)
continue;
int mn = n + 1;
for (int j = n; j >= i; j--)
{
ma[a[j]]++;
if (ma[a[j]] == 1)
{
mn = j;
}
else
break;
}
ans = min(ans, mn - i);
}
cout << ans << endl;
return 0;
}