题目
对于一个长度为 的整数数列: ,我们称之为接龙数列当且仅当 的首位数字恰好等于 的末位数字 ( )。
例如 是接龙数列; 不是接龙数列,因为 的首位数字不等于 的末位数字。
所有长度为 的整数数列都是接龙数列。
现在给定一个长度为 的数列 ,请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?
输入格式
第一行包含一个整数 。
第二行包含 个整数 。
输出格式
一个整数代表答案。
数据范围
对于 的数据, 。
对于 的数据, 。
对于 的数据, , 。所有 保证不包含前导 。
输入样例:
5
11 121 22 12 2023
输出样例:
1
样例解释
删除 ,剩余 是接龙数列。
解题
方法一:动态规划
思路
把问题「最少从中删除多少个数,可以使剩下的序列是接龙序列」转换为「找出序列中的最长接龙子序列」,再拿序列总长度减去最长接龙子序列即得到最少要删除的数。
找最长xx子序列不免会想到 LIS问题 ,事实上把动态转移的条件改一下就可以用在该题上了,如下:
动态规划:
- 状态定义: 表示所有以数组中第 个数结尾的接龙子序列的最大长度;
- 状态转移:当 的末尾数字等于 的首位数字时, ;
- 初始状态: 。
这样做的时间复杂度是 ,只能过前 的数据,还需要下面介绍的优化。
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], f[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
for (int i = 0; i < n; ++i) {
f[i] = 1;
int h = a[i];
while (h / 10) h /= 10;
for (int j = 0; j < i; ++j) {
if (a[j] % 10 == h) f[i] = max(f[i], f[j] + 1);
}
}
int mx = 0;
for (int i = 0; i < n; ++i) mx = max(mx, f[i]);
printf("%d\n", n - mx);
return 0;
}
时间复杂度: 。
优化
发现相较于 可以以任意在数组中的数结尾来说,接龙序列只会以 结尾,于是可以维护一个长度为 的数组 , 表示以数字 结尾的最长接龙子序列的长度,这样以来, 就可以用 进行状态转移,同时 也可以用 来反向更新。
这样做可以优化掉内层循环,时间复杂度降为 ,可以过全部 的数据了。
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], f[N], g[10];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
for (int i = 0; i < n; ++i) {
f[i] = 1;
int h = a[i], t = a[i] % 10;
while (h / 10) h /= 10;
f[i] = g[h] + 1;
g[t] = max(g[t], f[i]);
}
int mx = 0;
for (int i = 0; i < n; ++i) mx = max(mx, f[i]);
printf("%d\n", n - mx);
return 0;
}
时间复杂度: 。
评论区