算法题 动态规划

1. 下楼梯

解法 :

因为上楼和下楼是⼀个可逆的过程,因此我们可以把下楼问题转化成上到第n个台阶,⼀共有多少种方案。

  1. 状态表⽰: dp [ i ] 表⽰:⾛到第 i 个台阶的总⽅案数。 那最终结果就是在 dp [ n ] 处取到。
  2. 状态转移⽅程: 根据最后⼀步划分问题,⾛到第 i 个台阶的⽅式有三种:
    i − 1 台阶向上⾛ 1 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 1] ;
    i − 2 台阶向上⾛ 2 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 2] ;
    i − 3 台阶向上⾛ 3 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 3] ;
    综上所述, dp[i] = dp[i− 1] + dp[i− 2] + dp[i− 3] 。
  3. 初始化: 填位置的值时,⾄少需要前三个位置的值,因此需要初始化,然后从i = 3开始填。
    dp [0] = 1, dp [1] = 1, dp [2] = 2,或者初始化 dp [1] = 1, dp [2] = 2, dp [3] = 4 ,然后从 i = 4 开始填。
  4. 填表顺序 : 从左往右。

代码:

cpp 复制代码
#include<iostream>
using namespace std;
typedef long long LL;

const int N = 60;

int n;  
LL dp[N];  //f[i]表示当有i个台阶时,一共有多少种方案

int main()
{
	cin >> n;
	//初始化
	dp[1] = 1;dp[2] = 2;dp[3] = 4;
	for (int i = 4;i <= n;i++)
	{
		dp[i] = dp [i - 1] + dp[i - 2] + dp[i - 3];
	}

	cout << dp[n] << endl;

	return 0;
}

动态规划的空间优化:
我们发现,在填写dp[i]的值时,我们仅仅需要前三个格⼦的值,第 个及其之前的格⼦的值已经毫⽆⽤处了。因此,可以⽤三个变量记录i位置之前三个格⼦的值,然后在填完i位置的值之后,滚动向后更新。

cpp 复制代码
#include<iostream>
using namespace std;
typedef long long LL;

const int N = 60;
int n;  

int main()
{
	cin >> n;
	//初始化
	LL a = 1, b = 1, c = 2;
	for (int i = 3;i <= n;i++)
	{
		LL t = a + b + c;
		a = b;
		b = c;
		c = t;
	}
	if (n == 1)
		cout << b << endl;
	else
		cout << c << endl;

	return 0;
}

2. 数字三角形

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 1010;
int r;
int a[N][N];   //原始数字的数组
int dp[N][N];  //相加之后的数字的数组

int main()
{
	cin >> r;   //一共有r行
	for (int i = 1;i <= r;i++)
	{
		for (int j = 1;j <= i;j++)   //一定是j<=i而不是j<=r
		{
			cin >> a[i][j];
		}
	}
	for (int i = 1;i <= r;i++)
	{
		for (int j = 1;j <= i;j++)
		{
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
		}
	}
	int ret = 0;
	for (int j = 1;j <= r;j++)  
	{
		ret = max(ret,dp[r][j]);  //在第r行的第j列中通过max筛选出最大的数字
	}
	cout << ret << endl;
	return 0;
}

解法:

1. 状态表示 : dp[i][j] 表示: 走到 [i,j] 位置的最大权值。那最终结果就是在 dp 表的第 n 行中, 所有元素的最大值。
2. 状态转移方程 : 根据最后一步划分问题, 走到 [i,j] 位置的方式有两种:

a. 从 [i-1,j] 位置向下走一格, 此时走到 [i,j] 位置的最大权值就是 dp[i-1][j] ;

b. 从 [i-1,j-1] 位置向右下走一格, 此时走到 [i,j] 位置的最大权值就是 dp[i-1][j-1] ;

综上所述, 应该是两种情况的最大值再加上 [i,j] 位置的权值:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + a[i][j] 。
3. 初始化 : 因为 dp 表被 0 包围着, 并不影响我们的最终结果, 因此可以直接填表。
4. 填表顺序 : 从左往右填写每一行, 每一行从左往右。

思考, 如果权值出现负数的话, 需不需要初始化?

此时可以全都初始化为 -∞ , 负无穷大在取 max 之后, 并不影响最终结果。

代码:

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 1010;   //防止数组越界
int r;
int a[N][N];   //原始数字的数组
int dp[N][N];  //数字相加后的数组,也就是最终数组

int main()
{
	cin >> r;
	for (int i = 1;i <= r;i++)
	{
		for (int j = 1;j <= i;j++)  //一定是j<=i而不是j<=r
		{
			cin >> a[i][j];
		}
	}
	for (int i = 1;i <= r;i++)
	{
		for (int j = 1;j <= i;j++)
		{
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
            //状态转移方程:当前位置的结果只能通过自己前面和
            //左前方的数字两个中较大的数组再加上自己本身得到
		}
	}
	int ret = 0;
	for (int j = 1;j <= r;j++)
	{
		ret = max(ret,dp[r][j]);  //在第r行的第j列通过max筛选出最大的数字
	}
	cout << ret << endl;
	return 0;
}

动态规划的空间优化:

我们发现, 在填写第 i 行的值时, 我们仅仅需要前一行的值, 并不需要第 i-2 以及之前行的值。

因此, 我们可以只用一个一维数组来记录上一行的结果, 然后在这个数组上更新当前行的值。

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 1010;
int r;
int a[N][N];
int dp[N];   //优化后是一位数组

int main()
{
	cin >> r;
	for (int i = 1;i <= r;i++)
	{
		for (int j = 1;j <= i;j++)
		{
			cin >> a[i][j];
		}
	}
	// 空间优化
	for (int i = 1; i <= r; i++)
	{
		for (int j = i; j >= 1; j--) // 修改⼀下遍历顺序
		{
			dp[j] = max(dp[j], dp[j - 1]) + a[i][j];
		}
	}
	int ret = 0;
	for (int j = 1;j <= r;j++)
	{
		ret = max(ret, dp[j]);
	}
	cout << ret << endl;
	return 0;
}

3. 台阶问题

斐波那契数列模型

解法:

  1. 状态表⽰: dp [ i ] 表⽰:⾛到 i 位置的⽅案数。 那么 dp [ n ] 就是我们要的结果。
  2. 状态转移⽅程: 可以从 ikji − 1 区间内的台阶⾛到 i 位置,那么总⽅案数就是所有的dp [ j ] 累加在⼀ 起。 注意 ik 不能⼩于 0 。
  3. 初始化: dp [0] = 1 ,起始位置,为了让后续填表有意义。
  4. 填表顺序: 从左往右。

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e5 + 10, MOD = 1e5 + 3;

int n, k;
int dp[N];

int main()
{
	cin >> n >> k;
	dp[0] = 1;
	for (int i = 1;i <= n;i++)
	{
		for (int j = 1;j <= k && i-j> = 0;j++)
		{
			dp[i] = (dp[i] + dp[i - j]) % MOD;
		}
	}
	cout << dp[n] << endl;

	return 0;
}

总结

本文介绍了三个动态规划问题的解法。首先讨论下楼梯问题,通过状态转移方程dp[i]=dp[i-1]+dp[i-2]+dp[i-3]计算到第n个台阶的方案数,并给出空间优化方案。其次分析数字三角形问题,使用dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]求最大路径和,同样提供空间优化方法。最后是台阶问题的斐波那契数列模型解法,通过累加前k步的方案数求解。每个问题都详细说明了状态表示、转移方程、初始化和填表顺序,并给出了对应的C++代码实现。

相关推荐
水蓝烟雨2 小时前
3337. 字符串转换后的长度 II
算法·leetcode
MegaDataFlowers2 小时前
SiliconCompiler workflow
算法
_日拱一卒2 小时前
LeetCode:226翻转二叉树
数据结构·算法·leetcode
BirdenT2 小时前
20260424紫题训练
c++·算法
还是阿落呀2 小时前
基本控制结构
开发语言·c++·算法
样例过了就是过了2 小时前
LeetCode热题100 最长有效括号
c++·算法·leetcode·动态规划
wayz113 小时前
Day 18:Keras深度学习框架入门
人工智能·深度学习·神经网络·算法·机器学习·keras
一行代码一行诗++3 小时前
C语言中if的使用
c语言·c++·算法
AI科技星3 小时前
《基于 1 的 N 维分形与对称统一理论》
人工智能·算法·机器学习·数学建模·数据挖掘