【状态机动态规划 组合数学】3154. 到达第 K 级台阶的方案数|2071

本文涉及知识点

组合数学 状态机动态规划
动态规划汇总
组合数学汇总

LeetCode 100298. 到达第 K 级台阶的方案数

给你有一个 非负 整数 k 。有一个无限长度的台阶,最低 一层编号为 0 。
虎老师有一个整数 jump ,一开始值为 0 。虎老师从台阶 1 开始,虎老师可以使用 任意 次操作,目标是到达第 k 级台阶。假设虎老师位于台阶 i ,一次 操作 中,虎老师可以:
向下走一级到 i - 1 ,但该操作 不能 连续使用,如果在台阶第 0 级也不能使用。
向上走到台阶 i + 2jump 处,然后 jump 变为 jump + 1 。
请你返回虎老师到达台阶 k 处的总方案数。
注意 ,虎老师可能到达台阶 k 处后,通过一些操作重新回到台阶 k 处,这视为不同的方案。
示例 1:
输入:k = 0
输出:2
解释:
2 种到达台阶 0 的方案为:
虎老师从台阶 1 开始。
执行第一种操作,从台阶 1 向下走到台阶 0 。
虎老师从台阶 1 开始。
执行第一种操作,从台阶 1 向下走到台阶 0 。
执行第二种操作,向上走 20 级台阶到台阶 1 。
执行第一种操作,从台阶 1 向下走到台阶 0 。
示例 2:
输入:k = 1
输出:4
解释:
4 种到达台阶 1 的方案为:
虎老师从台阶 1 开始,已经到达台阶 1 。
虎老师从台阶 1 开始。
执行第一种操作,从台阶 1 向下走到台阶 0 。
执行第二种操作,向上走 20 级台阶到台阶 1 。
虎老师从台阶 1 开始。
执行第二种操作,向上走 20 级台阶到台阶 2 。
执行第一种操作,向下走 1 级台阶到台阶 1 。
虎老师从台阶 1 开始。
执行第一种操作,从台阶 1 向下走到台阶 0 。
执行第二种操作,向上走 20 级台阶到台阶 1 。
执行第一种操作,向下走 1 级台阶到台阶 0 。
执行第二种操作,向上走 21 级台阶到台阶 2 。
执行第一种操作,向下走 1 级台阶到台阶 1 。
提示:
0 <= k <= 109

枚举jump,计算back

我们称第一种方式为后退,第二种方式为跳跃。跳跃jump次移动的距离为 (1 << jump) -1 ,jump为0也符合。
如果后退了back次,则最终位置为: 1 + (1 << jump) -1 -back 如果能移到k,则 back = 1 + (1 << jump) -1 - k 。
如果back 小于0或大于jump+1则非法。 跳跃前,可以后退一次。每次跳跃后,可以后退一次。
如果back 为0,则方案数+1。否则要动态规划。
jump只需要枚举到40(可能更少),因为: 1 + (1LL << 40)-1 -back 远远大于k

动态规划

枚举jump,计算出back后,通过动态规划讨论方案数。

动态规划的状态表示

pre[j] 表示跳跃i次后,后退j次合法方案的数量。
dp[j] 及时pre[j+1]。
空间复杂度: 如果使用滚动向量,则空间复杂度为O(back);不使用滚动向量,空间复杂度为O(jump × \times ×bakc)。

动态规划的转移方程

{ d p [ j ] + = p r e [ j ] 不后退 d p [ j + 1 ] + = p r e [ j ] 后退 ( j + 1 < = b a c k ) 且当前位置大于 0 \begin{cases} dp[j] += pre[j] && 不后退 && \\ dp[j+1] += pre[j] && 后退 && (j+1 <= back) 且当前位置大于0 \\ \end{cases} {dp[j]+=pre[j]dp[j+1]+=pre[j]不后退后退(j+1<=back)且当前位置大于0
通过前置状态更新后置状态。每个状态更新的时间复杂度是:O(1),故总时间复杂度是:O(jump × \times ×bakc)

动态规划的初始值

pre[0]=pre[1] = 1 ,其它全为0。

动态规划的填表顺序

i = 1 To jump

动态规划的返回值

pre.back

动态规划的代码

核心代码

class Solution {
public:
	int waysToReachStair(int k) {
		int iRet = 0;	
		for (int jump = 0; jump <= 40; jump++) {
			const long long hasJump = (1LL << jump) - 1;	
			const long long back = 1 + hasJump - k;
			if ((back < 0) || (back > 1 + jump)) { continue; }
			if (0 == back) { iRet++; continue; }
			vector<int> pre(back + 1);
			pre[0] = 1;
			pre[1] = 1;
			for (int i = 1; i <= jump; i++) {				 
				vector<int> dp(back + 1);
				for (int j = 0; j < pre.size(); j++) {
					const long long llPosAfterJump = 1 + (1LL << i) - 1 - j;
					dp[j] += pre[j]; //不后退
					if ((llPosAfterJump > 0)&&(j+1 < pre.size())) {
						dp[j + 1] += pre[j];
					}
				}
				dp.swap(pre);
			}
			iRet += pre.back();
		}
		return iRet;
	}
};

测试用例

int main()
{	
	int k;
	{
		Solution slu;
		vector<int> ans = {2,4,4,3,2,4,6,4,1,0};
		for (int k = 0; k < 10; k++)
		{
			auto res = slu.waysToReachStair(k);
			Assert(res, ans[k]);
		}
	}	
}

组合数学

周赛时不用组合数学的原因,是担心后退后位置为负而非法。实际上这种可能不存在。
共有jump+1个后退位,每个后退位之前,都有个大于等于1的数。就算全部后退位都后退,也不会变成负数。
共有:C j u m p + 1 b a c k _{jump+1}^{back} jump+1back

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值