算法五:动态规划(数字三角形、最长上升子序列、最长公共子序列、神奇的口袋、斐波那契数列记忆化搜索、数塔、最大连续子序列和、最长不下降子序列(LIS))

这次我换一种方式,先给出例题再归纳算法方法

例题一、数字三角形

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
递归代码如下:

#include<stdio.h>

#define MAX 101

int max(int a, int b)
{
	return a > b ? a : b;
}

int D[MAX][MAX];
int n;

int MaxSum(int i, int j)
{
	if(i == n)
		return D[i][j];
	int x = MaxSum(i + 1, j);
	int y = MaxSum(i + 1, j + 1);
	return max(x, y) + D[i][j];
}

int main()
{
	int i, j;
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= i; j++)
		{
			scanf("%d", &D[i][j]);
		}
	}
	printf("%d\n", MaxSum(1, 1));
	return 0;
}

在这里插入图片描述
为什么超时?
在这里插入图片描述
改进策略:
在这里插入图片描述
优化版:记忆递归型动归程序

#include<stdio.h>

#define Max 101

int max(int a, int b)
{
	return a > b ? a : b;
}

int D[Max][Max];
int n;
int maxSum[Max][Max];		//用来存放每一个数字到底边的最大和 

int MaxSum(int i, int j)
{
	if(maxSum[i][j] != -1)
		return maxSum[i][j];
	if(i == n)
		maxSum[i][j] = D[i][j];
	else
	{
		int x = MaxSum(i + 1, j);
		int y = MaxSum(i + 1, j + 1);
		maxSum[i][j] = max(x, y) + D[i][j];
	}
	return maxSum[i][j];
}

int main()
{
	int i, j;
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= i; j++)
		{
			scanf("%d", &D[i][j]);
			maxSum[i][j] = -1;
		}
	}
	printf("%d\n", MaxSum(1, 1));
	return 0;
}

递推方案:
在这里插入图片描述
详细C代码如下:

#include<stdio.h>

#define Max 101
int D[Max][Max];
int maxsum[Max][Max] = {0};

int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int i, j;
	int n;
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= i; j++)
		{
			scanf("%d", &D[i][j]);
		}
	}
	for(i = 0; i <= n; i++)
	{
		maxsum[n][i] = D[n][i];
	}
	for(i = n - 1; i >= 1; i--)
	{
		for(j = 1; j <= i; j++)
			maxsum[i][j] = D[i][j] + max(maxsum[i + 1][j], maxsum[i + 1][j + 1]);
	}
	printf("%d\n", maxsum[1][1]);
	return 0;
} 

我们可以把空间优化一下,把maxsum转化为一维数组,节省空间
在这里插入图片描述

#include<stdio.h>

#define Max 101

int max(int a, int b)
{
	return a > b ? a : b;
}

int D[Max][Max];
int maxsum[Max] = {0};

int main()
{
	int i, j;
	int n;
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= i; j++)
		{
			scanf("%d", &D[i][j]);
		}
	}
	
	for(i = 1; i <= n; i++)
		maxsum[i] = D[n][i];
	
	for(i = n - 1; i >= 1; i--)
	{
		for(j = 1; j <= i; j++)
		{
			maxsum[j] = D[i][j] + max(maxsum[j], maxsum[j + 1]);
		}
	}
	
	printf("%d\n", maxsum[1]);
	
	
	return 0;
}

这个题还可以进一步空间优化,指针法
让指针maxsum指向D[n],把D[n]的首地址给maxsum,maxsum[1]就是D[n][1],依次类推

#include<stdio.h>

#define Max 101
int D[Max][Max];
int n;
int *maxsum;

int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	int i, j;
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= i; j++)
		{
			scanf("%d", &D[i][j]);
		}
	}
	maxsum = D[n];
	for(i = n - 1; i >= 1; i--)
	{
		for(j = 1; j <= i; j++)
		{
			maxsum[j] = D[i][j] + max(maxsum[j], maxsum[j + 1]);
		}
	}
	printf("%d\n", maxsum[1]);
	return 0;
}

动归思路:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

动归的应用场景:

在这里插入图片描述

例题二、最长上升子序列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
也就是说通过a数组承接输入的N个数据,maxLen数组记载以第i个数为最右端的最大子序列,相应得出最大子序列

详细C代码如下:

#include<stdio.h>

#define Max 1010
int a[Max];
int maxLen[Max];

#define max(a, b) (a > b ? a : b)

void sort(int a[], int n)
{
	int i, j;
	int t = 0;
	for(i = 1; i < n; i++)
	{
		for(j = i + 1; j < n + 1; j++)
		{
			if(a[i] > a[j])
			{
				t = a[j];
				a[j] = a[i];
				a[i] = t;
			}
		}
	}
}

int main()
{
	int N;
	scanf("%d", &N);
		
	int i, j;
	for(i = 1; i <= N; i++)
	{
		scanf("%d", &a[i]);
		maxLen[i] = 1;
	}
	
	for(i = 2; i <= N; i++)
	{
		for(j = 1; j < i; j++)
		{
			if(a[i] > a[j])
				maxLen[i] = max(maxLen[i], maxLen[j] + 1);
		}
	}
		
	sort(maxLen, N);
	printf("%d\n", maxLen[N]);
	
	return 0;
}

部分运行结果如下:
在这里插入图片描述
关于动态规划,蓝桥杯还有很多相关题目,可以参考该博文蓝桥杯2018第九届C语言B组省赛习题题解——习题D.测试次数*(经典递推)

例题三、最长公共子序列***

在这里插入图片描述
在这里插入图片描述
思路:
在这里插入图片描述
在这里插入图片描述
详细C代码如下:

#include<stdio.h>
#include<string.h>

char s1[1000];
char s2[1000];
int maxLen[1000][1000];

#define max(a, b) (a > b ? a : b)

int main()
{
	gets(s1);
	gets(s2);
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	int i, j;
	for(i = 0; i <= len1; i++)
	{
		maxLen[i][0] = 0;
	}
	for(j = 0; j <= len2; j++)
	{
		maxLen[0][j] = 0;
	}
	
	for(i = 1; i <= len1; i++)
	{
		for(j = 1; j <= len2; j++)
		{
			if(s1[i - 1] == s2[j - 1])
			{
				maxLen[i][j] = maxLen[i - 1][j - 1] + 1;
			}
			else
			{
				maxLen[i][j] = max(maxLen[i - 1][j], maxLen[i][j - 1]); 
			}
		}
	}
	printf("%d\n", maxLen[len1][len2]);
	return 0;
} 

部分运行结果如下:
在这里插入图片描述

例题四、神奇的口袋

在这里插入图片描述
在这里插入图片描述
递归法:

#include<stdio.h>

int a[30];
int N;

int Ways(int w, int k)		//w为需要凑的体积 
{
	if(w == 0)
		return 1;
	if(k <= 0)				//没有物品可选 
		return 0;
	return Ways(w, k - 1) + Ways(w - a[k], k - 1);
}				//不选第k种物品和选第k种物品 

int main()
{
	scanf("%d", &N);
	int i;
	for(i = 1; i <= N; i++)
	{
		scanf("%d", &a[i]);
	}
	printf("%d\n", Ways(40, N));
	return 0;
}

样例运行结果如下:
在这里插入图片描述
动归解法:

#include<stdio.h>
#include<string.h>

int main()
{
	int N;
	scanf("%d", &N);
	int ans[41][N + 1];
	int a[N + 1];
	memset(ans, 0, sizeof(ans));
	int i, j;
	for(j = 1; j <= N; j++)
	{
		scanf("%d", &a[j]);
		ans[0][j] = 1;
	}
	ans[0][0] = 1;	
	int v;
	
	for(v = 1; v <= 40; v++)
	{
		for(j = 1; j <= N; j++)
		{
			ans[v][j] = ans[v][j - 1];		
			if(v - a[j] >= 0)
				ans[v][j] += ans[v - a[j]][j - 1];
		}
	}
	
	printf("%d\n", ans[40][N]);
	return 0;
}

部分运行结果如下
在这里插入图片描述

斐波那契数列记忆化搜索

#include<bits/stdc++.h>
using namespace std;

#define maxn 100
int dp[maxn];

int F(int n)
{
	if(n == 0 || n == 1)
	{
		return 1;
	}
	if(dp[n] != -1)
	{
		return dp[n];
	}
	else
	{
		dp[n] = F(n - 1) + F(n - 2);
		return dp[n];
	}
} 

int main()
{
	memset(dp, -1, sizeof(int) * maxn);
	cout << F(5) << endl;
	return 0;
} 

运行结果:
在这里插入图片描述

数塔

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1000;

int f[maxn][maxn], dp[maxn][maxn];

int main()
{
	int n;
	cin >> n;
	
	//输入数塔 
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= i; j++)
		{
			cin >> f[i][j];
		}
	}
	//底层边界 
	for(int j = 1; j <= n; j++)
	{
		dp[n][j] = f[n][j];
	}
	
	for(int i = n - 1; i >= 1; i--)
	{
		for(int j = 1;j <= i; j++)
		{
			dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + f[i][j];
		}
	}
	
	cout << dp[1][1] << endl;
	return 0;
} 

样例运行结果:
在这里插入图片描述

最大连续子序列和

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int maxn = 10010;
int A[maxn], dp[maxn];

int main()
{
	int n;
	cin >> n;
	for(int i = 0; i < n; i++)
	{
		cin >> A[i];
	}
	
	dp[0] = A[0];
	
	for(int i = 1; i < n; i++)
	{
		dp[i] = max(A[i], dp[i - 1] + A[i]); 
	}
	int k = 0;
	for(int i = 1; i < n; i++)
	{
		if(dp[i] > dp[k])
		{
			k = i;
		}
	}
	
	cout << dp[k] << endl;
	return 0;
} 

样例运行结果:
在这里插入图片描述

最长不下降子序列(LIS)

#include<bits/stdc++.h>
using namespace std;

const int N = 100;
int A[N], dp[N];

int main()
{
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++)
	{
		cin >> A[i];
	}
	
	int ans = -1;
	for(int i = 1; i <= n; i++)
	{
		dp[i] = 1;
		for(int j = 1; j < i; j++)
		{
			if(A[i] >= A[j] && (dp[j] + 1 > dp[i]))
			{
				dp[i] = dp[j] + 1;
			}
		}
		ans = max(ans, dp[i]);
	}
	cout << ans << endl;
	return 0;
} 

样例运行结果:
在这里插入图片描述

最近我还会将算法博文更新丰富完善!如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持,下期更精彩!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值