<蓝桥杯>动态规划习题总结

总结计蒜客视频习题,附题目描述和解题思路及代码,持续更新…

1. 蒜头君爬楼梯2

题目描述
蒜头君很喜欢爬楼梯,这一次,他获得了一个特异功能,每次可以跳跃任意奇数的阶梯。比如他初始在楼底,跨越一个阶梯到达1号阶梯,或者跨越3个楼梯到达3号阶梯。为了选出一种最轻松的爬楼梯的方式,蒜头君想把所有不同的到达楼顶的方式都尝试一遍。对于-共有n个阶梯的楼梯,蒜头君一共有多少总方法从楼底到达楼顶。由于最后答案可能很大,输出最后的答案对100007取模的结果。

解题思路
利用动态规划,因为可以跳跃奇数,可按照奇数进行遍历,如初始位置 i ,则上一步可能为i - 1 ,i - 3, i-5, …

#include<bits/stdc++.h>
using namespace std;
const int mod=100007;
int a[10101];
int main()
{
    int n;
    cin>>n;
    a[0]=1;
    for(int i=1; i<=n; i++)
    {
        a[i]=0;
        for(int j=i-1; j>=0; j-=2) 
        /*j初始设为i-1!!!不能设初始j为i
     	*(动态规划:考虑从上一步开始)并在每次循环-2,可保证每次是按照1 3 5 7 9 ...递减循环
     	*逆序处理才能够遍历小于本台阶的加上奇数为这个数的数
		*/     
        {
            a[i]+=a[j];
            a[i]%=mod;
        }
    }
    cout<<a[n]<<endl;
    return 0;
}

2. 弹簧板(加强)

题目描述
有一个小球掉落在一串连续的弹簧板上,小球落到某-个弹簧板后, 会被弹到某一 个地点, 直到小被弹到弹簧板以外的地方。假设有n个连续的弹簧板,每个弹簧板占- -个单位距离, a[i]代表第i个弹簧板会把小球向前弹a[i]个距离。比如位置1的弹簧能让小球前进2个距离到达位置3。如果小球落到某个弹簧板后,经过一系列弹跳会被弹出弹簧板,那么小球就能从这个弹簧板弹出来。现在希望你计算出小球从任意一个弹簧板落下 ,最多会被弹多少次后,才会弹出弹簧板。
输入格式
第一个行输入一个n代表-共有n个弹簧板。第二行输入n个数字,中间用空格分开。第i个数字a[]代表第i个弹簧板可以让小球移动的距离。
数据约定:
对于50%的数据:1≤n≤1000,0< a[i]≤30。
对于100%的数据:1≤n≤10000,0< a[i] < 30。
输出格式
输出一个整数,代表小球最多经过多少次才能弹出弹簧板。|
样例输入
5
2 2 3 1 2
样例输出
3

解题思路
从i位置可以跳到a[i]+i的位置,最后一个肯定是只需要跳一次就跳出去,那么
从i位置跳出去的次数就是从a[i]+i跳出的次数加上他这次跳的一次

从最后一个弹簧板倒着往前推,用dp[i]数组记录下在i处下落的弹簧多少次可以出去,这样dp[i] = 1 + dp[i+a[i]] ;
i+a[i] 可得到下标,到达最后一个弹簧板后还需要再弹跳一次,故 +1

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100110],dp[100110];
memset(dp, 0, sizeof(dp));      //必须要有这一步
int n;
int res = 0;
 
int main() {
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) {
        scanf("%d",&a[i]);
    }
    for(int i = n;i >= 1;i--) {  //从n开始递推       
        dp[i] = 1 + dp[i+a[i]];  //dp[i+a[i]] 表示能够弹到的位置  1 表示这次我去弹的次数
        res = max(res,dp[i]);
    }
    printf("%d\n",res); 
    return 0;       
}

3. 蒜头君的新游戏

问题描述
工作空闲之余,蒜头君经常带着同事们做游戏,最近蒜头君发明了一个好玩的新游戏:n 位同事围成一个圈,同事 A 手里拿着一个兔妮妮的娃娃。蒜头君喊游戏开始,每位手里拿着娃娃的同事可以选择将娃娃传给左边或者右边的同学,当蒜头君喊游戏结束时,停止传娃娃。此时手里拿着娃娃的同事即是败者。 玩了几轮之后,蒜头君想到一个问题:有多少种不同的方法,使得从同事 A 开始传娃娃,传了 m 次之后又回到了同事 A 手里。两种方法,如果接娃娃的同事不同,或者接娃娃的顺序不同均视为不同的方法。例如1−>2−>3−>1 和 1->3->2->1 是两种不同的方法。
输入格式
输入一行,输入两个整数n,m(3≤n≤30,1≤m≤30),表示一共有 n 位同事一起游戏,一共传 m 次娃娃。
输出格式
输出一行,输出一个整数,表示一共有多少种不同的传娃娃方法。
样例输入
3 3
样例输出
2

解题思路
1.定义数组:dp[i][j]–传了i次,现在在j手上
2.边界条件:
传到1号手里,可由2号和n号传递给1号,dp[i][j]=dp[i-1][2]+dp[i-1][n];
传到n号手里,可由1号和n-1号传递给n号,dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
3.状态方程:dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];

int main(){
    int n,m;
    int dp[35][35]; //dp[i][j]传了i次,现在在j手上
    cin>>n>>m;
    memset(dp,0,sizeof(dp));
    dp[0][1]=1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(j==1){
                dp[i][j]=dp[i-1][2]+dp[i-1][n];
            }else if(j==n){
                dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
            }else{
                dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
            }
        }
    } 
    cout<<dp[m][1]<<endl;
    return 0;
}

4. 计蒜客习题:逃生

题目描述
在这里插入图片描述输入格式
第一行依次输入整数 n,m,x,y,v,c(1<n,m≤1000,1≤x≤n,1≤y≤m,1≤v≤c≤10000), 其中 n,m 代表地图大小,(x,y) 代表蒜头君的初始位置,v 代表蒜头的初始化血量,c 代表蒜头的生命值上限。接下来 n 行,每行有 m 个数字,代表地图信息。(每个数字的绝对值不大于100,地图中蒜头君的初始位置的值一定为 0)
输出格式
一行输出一个数字,代表成功逃生最多剩余的血量,如果失败输出 −1。
样例输入
4 4
3 2 5 10
1 2 3 4
-1 -2 -3 -4
4 0 2 1
-4 -3 -2 -1
样例输出
10

解题思路
在这里插入图片描述

#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
//给了四个方向,我不知道从哪一个方向走,那我定义四个方向数组,求出往四个方向走的最大值
const int INF = -3356;
const int maxn = 1010;
int a[maxn][maxn];
int dp[maxn][maxn];
int main(){
    int n, m, x, y, v, c;       //n行m列x,y初始位置,v初始血量,c最大血量
    cin >> n >> m >> x >> y >> v >> c;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
        }
    }
    //定义四个方向!!!!!!
    int xx[4] = { -1,-1,1,1 };
    int yy[4] = { -1,1,-1,1 };
    for (int t = 0; t < 4; t++) {
        for (int i = x; i > 0 && i <= n; i -= xx[t]) {
            for (int j = y; j > 0 && j <= m; j -= yy[t]) {
                if (i == x && j == y) {
                    dp[i][j] = v;
                }
                else if (i == x) {	//在同一行,可推断是在左右方向走
                    dp[i][j] = min(c, dp[i][j + yy[t]] + a[i][j]);
                }
                else if (j == y){	//在同一列,在上下方向走
                    dp[i][j] = min(c, dp[i + xx[t]][j] + a[i][j]);
                }
                else{
                    dp[i][j] = min(c, max(dp[i + xx[t]][j], dp[i][j + yy[t]]) + a[i][j]);
                }
                if (dp[i][j] <= 0) {
                    dp[i][j] = -INF;
                }
            }
        }
    }

    int ans = max(max(dp[1][1], dp[1][m]), max(dp[n][1], dp[n][m]));        //利用max函数求出四个出口的最大值
    if (ans <= 0) {
        cout << "-1" << endl;
    }else{
        cout << ans << endl;
    }
    return 0;
}

5. 一维消消乐

题目描述
有 n 颗珠子排成一排,每一颗珠子有一个价值 wi(可能是负数)。游戏是这样,你可以选择如若干对相邻的珠子,让他们同时消去。每一对珠子的消失,都会使得总分数加上两颗珠子相乘的分数。注意,每个珠子只能消一次,并且珠子消去以后,还会占位。
输入格式
输入第一行一个整数n(1≤n≤10000)。
接下来一行输入 n 个整数 (−1000≤ wi ≤1000)。
输出格式
输出最大的分数。
样例输入:
8
-9 -5 -4 -2 4 -5 -4 2
样例输出:
73

const int maxn=10010;
int dp[maxn][2]     //1代表和前面组合 0代表没有和前面组合
int w[maxn]
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    dp[1][0]=0;
    for(int i=2;i<=n;++i){
        dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
        dp[i][1]=dp[i-1][0]+w[i]*w[i-1];
    }
    cout<<max(dp[n][0],dp[n][1])<<endl;
    return 0;
}

6. 数组分组

题目描述
一个长度为n的数组a,我们可以把它分成任意组,每一组是一 段连续的区间。比如数组1,2, 3,4,5可以
分成(1,2), (3, 4, 5)两个组。每个分组都有一个权值,这个权值就是分组里面每个数的乘积对1000取模的结果。对于数组a的一个分组方案,总权值就是每个分组的权值和。那么对于数组a,分组以后最大的权值和是多少?
输入格式.
输入第一张一个整数n(1≤n≤1000)。接下来一行n个整数,表示数组a,数组中每个元素都小于等于100。
输出格式
数组最大的分组权值和。
样例输入
7
52 26 1 36 72 48 43
样例输出
1596

//1、预处理过程,2、如何比较找出最大权值和
const int maxn=1010;
const int mod=1000;
int pre[maxn][maxn];
int a[maxn];
int dp[maxn];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){      //预处理,每一段分组造成的权值和
        pre[i][i]=a[i];	
        for(int j=i+1;j<=n;++j){
            pre[i][j]=pre[i][j-1]*a[j]%mod;	//pre[i][j]代表区间i到j所有数的乘积 
        } 
    }
    dp[0]=0;	//dp[i]代表考虑到第i个数时的最大和
    dp[1]=a[1];
    for(int i=2;i<=n;++i){
        for(int j=0;j<i;++j){
            //求出最大值
            dp[i]=max(dp[i],dp[j]+pre[j+1][i]);	//dp[j]+pre[j+1][i]前j位划分成一组,再从j+1到i位置划分成一组
        }
    }
    cout<<dp[n]<<endl;
}

7. 最大子段和

题目描述
输出一段数字,求数字串中连续子和的段最大值
输入
6
-1 11 -4 13 -5 -6
输出
20

typedef inf 9999;
int main(){
	int n,num[105];
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>num[i];
	}
	int ans=-inf;
	for(int i=0;i<n;i++){
		ans=max(ans,num[i]);
	}
	//求ans是考虑全部数字为负数情况 
	if(ans<=0){
		cout<<ans<<endl;
	}else{
		int sum=0;
		for(int i=0;i<n;i++){
            //一定要去判断sum+num[i]是否小于0,若小于0,则舍弃负数子段,使sum=0
			if(sum+num[i]<0){
				sum=0;
			}else{
				sum+=num[i];
			}
            //一定要求max,max里面的ans保留了上一次球取的最大值,
			ans=max(ans,sum);//
		}
	}
	cout<<ans<<endl;
	
	return 0;
}

**

8. 最大矩阵和

题目描述
在这里插入图片描述
解题思路
这道题要求求最大子矩阵之和,这道题其实和最大子段和很像。首先我们用前缀和求出每一列上前i行的和,之后,我们先要把行的上界和下界固定下来,然和把每一列的数都加起来。想象一下其实就是相当于把一个矩阵压缩成一列数,然后用求最大子段和的方法求出最大值。随后,我们需要尝试每一种可能,也就是每一种上下界不同的情况下的最大矩阵和,找出最大的输出即可。
代码

long long num[401][401], presum[401][401];
int main() {
	int N, M;
	long long sum, ans;
	cin >> N >> M;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= M; j++) {
			cin >> num[i][j];
			ans = max(ans, num[i][j]);
		}
	}
	if (ans <= 0) {		//num最大值都小于等于0,直接返回ans
		cout << ans << endl;
	}
	else {
		for (int i = 1; i <= N; i++) {		//预处理
			for (int j = i; j <= M; j++) {
				presum[i][j] = presum[i - 1][j] + num[i][j];//第i行j列的前缀和
			}
		}
		for (int i = 1; i <= N; i++) {//i为矩阵上边界
			for (int j = i; j <= N; j++) {//j为矩阵下边界
				sum = 0;
				for (int k = 1; k <= M; k++) {//一维最大子段和,利用前缀和快速计算
					if (sum + presum[j][k] - presum[i - 1][k] < 0) {
						sum = 0;
					}
					else {
						sum += presum[j][k] - presum[i - 1][k];
					}
					ans = max(ans, sum);
				}
			}
		}
		cout << ans << endl;
	}

	return 0;
}

9. 墙壁涂色

问题描述
在这里插入图片描述
输入格式
一个整数 n,表示房间被划分成多少部分。(1≤n≤50)
输出格式
一个整数,表示给墙壁涂色的合法方案数。
样例输入
4
样例输出
18

解题思路
第一个和第n-1个不同,则第n个需要和这两个再不同,第n位置只有一种选择a[i-1]
第一个和第n-1个相同,则第n-2个要和第一个不同, 第n项有两种颜色可以取,同时因为第n块和第n-1块不同色,所以a[n]=2*a[n-2]

typedef long long ll;
int main(){
    int n;
    ll a[51];
    a[1]=3;
    a[2]=6;
    a[3]=6;
    for(int i=4;i<=50;i++){
        a[i]=a[i-1]+a[i-2]*2;
    }
    cin>>n;
    cout<<a[n]<<endl;
    return 0;
}
/*第一个和第n-1个不同,则第n个需要和这两个再不同,第n位置只有一种选择a[i-1]
  第一个和第n-1个相同,则第n-2个要和第一个不同, 第n项有两种颜色可以取,同时因为第n块和第n-1块不同色,所以a[n]=2*a[n-2]
*/

10. 小朋友过桥

题目描述
在这里插入图片描述
输入:
两行数据:第一行为小朋友个数n,第二行有n个数,用空格隔开,分别是每个小朋友过桥的时间。
输出:
一行数据:所有小朋友过桥花费的最少时间。
样例输入
4
1 2 5 10
输出
17

解题思路:
我们先将所有人按花费时间递增进行排序,假设前i个人过河花费的最少时间为opt[i],那么考虑前i-1个人过河的情况,即河这边还有1个人,河那边有i-1个人,并且这时候手电筒肯定在对岸,
所以opt[i] = opt[i-1] + a[1] + a[i] (让花费时间最少的人把手电筒送过来,然后和第i个人一起过河)

如果河这边还有两个人,一个是第i号,另外一个无所谓,河那边有i-2个人,并且手电筒肯定在对岸,所以opt[i] = opt[i-2] + a[1] + a[i] + 2a[2] (让花费时间最少的人把电筒送过来,然后第i个人和另外一个人一起过河,由于花费时间最少的人在这边,所以下一次送手电筒过来的一定是花费次少的,送过来后花费最少的和花费次少的一起过河,解决问题)
所以 opt[i] = min{opt[i-1] + a[1] + a[i] , opt[i-2] + a[1] + a[i] + 2
a[2] }。
来看一组数据 四个人过桥花费的时间分别为 1 2 5 10
具体步骤是这样的:
第一步:1和2过去,花费时间2,然后1回来(花费时间1);
第二歩:3和4过去,花费时间10,然后2回来(花费时间2);
第三部:1和2过去,花费时间2,总耗时17。

int a[1010];
int f[1010];    //前多少个人过去了,且手电筒在对岸
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    } 
    sort(a,a+n);
    f[0]=a[0];
    f[1]=a[1];
    for(int i=2;i<n;i++){
        f[i]=min(f[i-1]+a[0]+a[i],f[i-2]+a[0]+2*a[1]+a[i]);
        //0号回来接a[i],让0号带着i一起走
    }
    cout<<f[n-1]<<endl;
    return 0;
}

**

11. 最长上升子序列(LIS)

**
字符子串:字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。

字符子序列:字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,
但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是

测试用例
输入
6
3 2 6 1 4 5
输出
3
在这里插入图片描述

代码

int dp[110],a[110],n,ans=0;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		dp[i]=1;
		for(int j=1;j<i;j++){
			if(a[j]<a[i])
				dp[i]=max(dp[i],dp[j]+1);	//不断在j<i的情况下,对dp[i]取最大值 
		}
		ans=max(ans,dp[i]);					//在i不同值的情况下,求dp[i]最大值
	}
	cout<<ans<<endl;
	return 0;
}

**

12. 最长公共子序列(LCS)

**
在这里插入图片描述

代码:

int dp[110][110];
int main() {
	string a, b;
	memset(dp, 0, sizeof(dp));
	cin >> a >> b;
	int lena = a.size();
	int lenb = b.size();

	for (int i = 1; i <= lena; i++) {
		for (int j = 1; j <= lenb; j++) {
			if (a[i - 1] == b[j - 1]) {
				dp[i][j] = dp[i - 1][j - 1] + 1;
			}
			else {
				dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
			}
		}
	}
	cout << dp[lena][lenb] << endl;
	return 0;
}

13. 编辑距离

在这里插入图片描述
解题思路:
首先我们想到的第一种办法是搜索, 因为我们需要找到的是“最少”的操作次数,因此当S的长度为m, T的长度为n时,我们可以找到操作次数的界限:
(1)把T中的字符全删了,再添加S的全部字符,操作次数 m + n;
(2)把T中的字符长度变成m,再修改,操作次数最多 |m- n| + m.
但是我们发现,搜索的方式并不可行。因为搜索的空间是指数级的,这取决于S中字符数量的种类一如果 S是一个由小写字母组成的字符串,那么它的复杂度是O(26m),再思考一下,这个问题之所以难,是因为这个问题当中有诸如“添加”、“删除” 这些操作。我们不妨换一种理解方式,从字符串对齐的角度来理解这个问题。给定字符串S和T,我们要通过往这两个字符串当中加入一种特殊符号(这里用 - 代替), 来让这两个字符串的长度相同并且“对齐”,如果两个串相同的位置出现了不同的字符,那么就扣1分,我们要让两个字符串对齐的扣分尽量少。对于例子我们实际上采用了这样的对齐方式:

  1 2 3 4 5
S A B C F -
T D B - F G

那么我们看一下:
(1) S和T中的字符都为普通字符且相同(位置2,4)一不扣分。
(2)S和T中的字符都为普通字符但不同(位置1)一扣1分。
(3)S中为普通字符,T中为’-'(位置3)一扣1分。
(4)T中为普通字符,S中为‘-’(位置5)一扣1 分。
我们来看看前面的项目对应什么:
(1)直接对应;
(2)对应在T中修改该字符:
(3)对应在T中添加该字符;
(4)对应在T中删除该字符。
好了,感觉是不是和前面的LCS很像?令f(i, j)表示S的前i位和T的前j位对齐后的最少扣分,我们尝试看看四种情况:
(1)此时很明显S[i] = T[j],表示当前位置是对齐的,因此答案就是 f(i - 1,j - 1)。
(2)此时和(1)中类似,只不过S[i]≠T[j],那么在当前位置需要修改,因此答案就是 f(i - 1,j - 1) +1.
(3) S的前i位和T的前j - 1位对齐之后,在当前位置需要执行一次添加操作。 因此此时的答案应为 f(i,j - 1) + 1
(4)S的前i - 1位和T的前 j 位对齐之后,在当前位置需要执行一次删除操作。 因此此时的答案应为 f(i - 1,j) + 1
因此,我们可以推出递推式:
在这里插入图片描述
对于S的前0个字符,我们只能把T中的字符全部删掉。同样道理,对于T中的前0个字符,我们只能选择把T中
添加上S当前长度的字符。

代码:

int dp[110][110];
int main() {
	string a, b;
	memset(dp, 0, sizeof(dp));
	cin >> a >> b;
	int lena = a.size();
	int lenb = b.size();
	for (int i = 1; i <= lena; i++) {
		dp[i][0] = i;
	}
	for (int i = 1; i <= lenb; i++) {
		dp[0][i] = i;
	}
	for (int i = 1; i <= lena; i++) {
		for (int j = 1; j <= lenb; j++) {
			if (a[i - 1] == b[j - 1]) {
				dp[i][j] = dp[i - 1][j - 1];
			}
			else {
				dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
			}
		}
	}
	cout << dp[lena][lenb] << endl;
	return 0;
}

14. 蒜头君的最大子矩阵和

在这里插入图片描述

解题思路:
这道题要求求最大子矩阵之和,这道题其实和最大子段和很像。首先我们用前缀和求出每一列上前i行的和,之后,我们先要把行的上界和下界固定下来,然和把每一列的数都加起来。想象一下其实就是相当于把一个矩阵压缩成一列数,然后用求最大子段和的方法求出最大值。随后,我们需要尝试每一种可能,也就是每一种上下界不同的情况下的最大矩阵和,找出最大的输出即可。

代码:

long long num[401][401], presum[401][401];
int main() {
    int N, M;
    long long sum, ans;
    cin >> N >> M;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            cin >> num[i][j];
            ans = max(ans, num[i][j]);
        }
    }
    if (ans <= 0) {     //num最大值都小于等于0,直接返回ans
        cout << ans << endl;
    }
    else {
        for (int i = 1; i <= N; i++) {      //预处理
            for (int j = i; j <= M; j++) {
                presum[i][j] = presum[i - 1][j] + num[i][j];//第j列前i行总和
            }
        }
        for (int i = 1; i <= N; i++) {//i为矩阵上边界
            for (int j = i; j <= N; j++) {//j为矩阵下边界
                sum = 0;
                for (int k = 1; k <= M; k++) {//一维最大子段和,利用前缀和快速计算
                    if (sum + presum[j][k] - presum[i - 1][k] < 0) {
                        sum = 0;
                    }
                    else {
                        sum += presum[j][k] - presum[i - 1][k];
                    }
                    ans = max(ans, sum);
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}

15. 跳木桩

在这里插入图片描述

解题思路:
求最长不递增子序列

参考最长递增子序列, 从后往前找

代码:

int a[1010], dp[1010];
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int ans = 0;
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (a[j] >= a[i]) {//从后往前找不递增数
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        ans = max(ans, dp[i]);
    }
    cout << ans << endl;
    return 0;
}

16. 删除最少的元素

在这里插入图片描述

解题思路:

题目要求的数是先递减后递增

递减——最长不增子序列

递增——最长递增子序列

首先从前往后找出每一个位上的最长非递增子序列,然后从后往前找出每一位上的最长非递增子序列,

最后把前后位上的结果相加-1,因为要找出删除最少的元素数,所以 n - ans 即可

代码:

int a[1010], dp[2][1010];//i为0,正序求最长非递增子序列,1逆序求最长非递增子序列
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int ans = 0;
    for (int i = 0; i < n; i++) {
        dp[0][i] = 1;
        for (int j = 0; j < i; j++) {
            if (a[j] >= a[i]) { //正序
                dp[0][i] = max(dp[0][i], dp[0][j] + 1);
            }
        }
    }
    for (int i = n - 1; i >= 0; i--) {
        dp[1][i] = 1;
        for (int j = n - 1; j > i; j--) {
            if (a[j] >= a[i]) { //倒序
                dp[1][i] = max(dp[1][i], dp[1][j] + 1);
            }
        }
    }
    for (int i = 0; i < n; i++) {
        ans = max(ans, dp[0][i] + dp[1][i] - 1);
    }
    cout << n - ans << endl;
    return 0;
}
  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值