算法每日一题——接龙数列
题目介绍
![![[Pasted image 20250331094145.png]]](https://i-blog.csdnimg.cn/direct/4ecb9cdb4f12440f9a2489ceeee9bd08.png)

题目分析
- 暴力做法:20%的数据范围很小,如果时间不够或者想不出别的办法,可以用暴力循环拿20%的分数,只需要枚举从1到n 所有长度的数列,并判断是否为接龙数列,找出最长的接龙数列,在用原数列长度减去接龙数列长度即可。
- 正解DP:我们能发现,这道题很符合动态规划 选与不选 的经典问题,对于数列每个元素Ai,我们都要判断删还是不删,所以这道题很快就能想到用动态规划做。
设计DP数组
- 首先按照惯例,设计一个a【i】,表示前i项构成接龙数组要删的数量,发现不够用。
- 如何判断一个元素是否能删除呢?比如34,如果前一位末位元素不等于3,那么34是一定要删除的吗?显然不是,所以DP数组需要显示末位元素信息。保留34的话就向前找末尾为3的最长接龙数列,并更新末尾为4的当前最长数列,不保留就将末尾为3 的数列加一
- 所以DP数组定义为dp【n】【10】,a【i】【j】就表示前i项以j为结尾最长的接龙数列
状态转移方程
- 由上面的设计和分析,可以得出对于任意dp【i】【j】
- 不选 (删), dp【i】【j】 = dp【i-1】【j】;
- 对每个i(每多计算一个数列元素),更新末尾0-9的所有dp数组。
- 选(不删),dp【i】【back(a【i】)】= dp【i-1】【front(a【i】)】;
- back计算a【i】的末位,front计算a【i】的首位。
- 因为dp数组每个i都会更新所有j,直接找dp【i-1】。
初始化
- i = 0 即元素数为0时,dp【0】【j】 = 0
- 因为是每多一个元素更新一遍,所以只需要初始化没有元素时的dp数组就好
C++代码实现
#include<bits/stdc++.h>
using namespace std;
int arr[100010];
//dp数组的定义:dp【i】【j】表示前i项以j为结尾接龙数列的最小删除数
int dp[100010][15] = {1000000010};
int n;
int front(int n){
while(n >= 10){
n = n/10;
}
return n;
}
int back(int n){
return n%10;
}
int main(){
cin>>n;
for(int i = 1; i <= n; i++){
cin>>arr[i];
}
//初始化DP数组
for(int i = 0; i < 10; i++){
dp[0][i] = 0;
}
//开始遍历
for(int i = 1; i <= n; i++){
//不选,就是说删了目前这个元素,则元素末尾依旧保持原样,有0——9共10种情况
for(int j = 0; j < 10; j++){
dp[i][j] = dp[i-1][j] + 1;
}
int f = front(arr[i]);
int b = back(arr[i]);
//选,那没有删除这个动作,那么以a【i】最后一位为尾元素,等于dp上一位以a【i】头一位的伪元素
//选还是不选,要进行比较
//如果不选,意味着dp[i][back] = dp[i-1][back]+1
//选意味着dp[i][back] = dp[i-1][front],对这两者进行大小比较即可
dp[i][b] = min(dp[i-1][f], dp[i][b]);
}
int min = 1000000010;
for(int i = 0; i < 10; i++){
if(dp[n][i] < min) min = dp[n][i];
}
cout<<min;
}