算法题 动态规划

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;
}

动态规划的空间优化:
我们发现,在填写dpi的值时,我们仅仅需要前三个格⼦的值,第 个及其之前的格⼦的值已经毫⽆⽤处了。因此,可以⽤三个变量记录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. 状态表示 : dpij 表示: 走到 i,j 位置的最大权值。那最终结果就是在 dp 表的第 n 行中, 所有元素的最大值。
2. 状态转移方程 : 根据最后一步划分问题, 走到 i,j 位置的方式有两种:

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

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

综上所述, 应该是两种情况的最大值再加上 i,j 位置的权值:
dpij = max(dpi-1j, dpi-1j-1) + aij
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;
}

总结

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

相关推荐
江畔柳前堤19 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore20 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考1 天前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX1 天前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面1 天前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号1 天前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽1 天前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI1 天前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者1 天前
征程6工具链模型X86推理方式说明
算法
地平线开发者1 天前
【征程6】校准量化中HistogramObserver解析
算法