算法每日一题——《接龙数列》 动态规划

算法每日一题——接龙数列

题目介绍

![[Pasted image 20250331094145.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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值