[LeetCode][面试题 08.01]三步问题(Java)(动态规划)

题目描述

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

输入:n = 3
输出:4
说明: 有四种走法

示例2:

输入:n = 5
输出:13

提示:

n范围在[1, 1000000]之间

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/three-steps-problem-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

典型动态规划,斐波那契数列

分解来看:
第一步:在第零步基础上跨一步;
第二步:在第零步上跨两步,在第一步上跨一步;
第三步:在第零步上跨三步,第一步上跨两步,第二步上跨三步;
第四步:第一步跨三步,第二步跨两步,第三步跨一步;
……
第N步:第N-3步跨三步,第N-2步跨两步,第N-1步跨一步;

这样就找到了规律,那么表达式就是
cnt[i] = cnt[i-1] + cnt[i-2] + cnt[i-3]。

因为要取模,MOD 1,000,000,007,比 int 最大值(231-1=2,147,483,647)小大约一半。

为了避免前三个数都是接近MOD的情况(三个数加一起再取模),应该对每两个数相加就取模。

所以表达式为:
cnt[i] = ((cnt[i-1] + cnt[i-2]) % MOD + cnt[i-3]) % MOD;

PS1:还有跨N阶台阶的变态台阶问题,可以去挑战下。
PS2:0ms的矩阵快速幂写法,先了解快速幂[3、快速幂 4、矩阵快速幂]
核心是找到关系矩阵

已知: f ( n ) = f ( n − 1 ) + f ( n − 2 ) + f ( n − 3 ) f(n)=f(n-1)+f(n-2)+f(n-3) f(n)=f(n1)+f(n2)+f(n3)

那么通过:
[ f ( n − 1 ) f ( n − 2 ) f ( n − 3 ) ] ∗ [ a 1 b 1 c 1 a 2 b 2 c 2 a 3 b 3 c 3 ] = [ f ( n ) f ( n − 1 ) f ( n − 2 ) ] \begin{bmatrix} f(n-1) & f(n-2) & f(n-3) \\ \end{bmatrix} * \begin{bmatrix} a1 & b1 & c1 \\ a2 & b2 & c2 \\ a3 & b3 & c3 \end{bmatrix}= \begin{bmatrix} f(n) & f(n-1) & f(n-2) \\ \end{bmatrix} [f(n1)f(n2)f(n3)]a1a2a3b1b2b3c1c2c3=[f(n)f(n1)f(n2)]

得到:
f ( n ) = a 1 f ( n − 1 ) + a 2 f ( n − 2 ) + a 3 f ( n − 3 ) f ( n − 1 ) = b 1 f ( n − 1 ) + b 2 f ( n − 2 ) + b 3 f ( n − 3 ) f ( n − 2 ) = c 1 f ( n − 1 ) + c 2 f ( n − 2 ) + c 3 f ( n − 3 ) f(n)=a_1f(n-1)+a_2f(n-2)+a_3f(n-3) \\ f(n-1)=b_1f(n-1)+b_2f(n-2)+b_3f(n-3) \\ f(n-2)=c_1f(n-1)+c_2f(n-2)+c_3f(n-3) f(n)=a1f(n1)+a2f(n2)+a3f(n3)f(n1)=b1f(n1)+b2f(n2)+b3f(n3)f(n2)=c1f(n1)+c2f(n2)+c3f(n3)

可知:
a 1 = 1 , a 2 = 1 , a 2 = 1 b 1 = 1 , b 2 = 0 , b 2 = 0 c 1 = 0 , c 2 = 1 , c 2 = 0 a_1=1,a_2=1,a_2=1 \\ b_1=1,b_2=0,b_2=0 \\ c_1=0,c_2=1,c_2=0 \\ a1=1,a2=1,a2=1b1=1,b2=0,b2=0c1=0,c2=1,c2=0

得到关系矩阵,就可以使用矩阵快速幂来计算了。细节就不写了

反思错误

一开始没注意到MOD接近 int 最大值,导致最后加出了负数。

后来验证数据时发现在 42 步出现了负数,再看前三步数据,都是接近MOD的值,才反应过来。

Java代码

// 1、正常数组存储
class Solution {
	private static final int MOD = 1000000007;
    public int waysToStep(int n) {
    	if(n<3) return n; 
    	int[] cnt = new int[n+1];
    	cnt[0] = 1;
     	cnt[1] = 1;
    	cnt[2] = 2;

    	for(int i=3;i<=n;++i){
    		cnt[i] = ((cnt[i-3] + cnt[i-2])%MOD + cnt[i-1])%MOD;
    	}
    	return cnt[n];
    }
}
// 2、滚动数组,用时间换空间,占用更小的数组
class Solution {
	private static final int MOD = 1000000007;
    public int waysToStep(int n) {
    	if(n<3) return n; 
    	int[] cnt = new int[4];
    	cnt[0] = 1;
     	cnt[1] = 1;
    	cnt[2] = 2;
    	for(int i=3;i<=n;++i){
    		cnt[i%4] = ((cnt[(i-3)%4] + cnt[(i-2)%4])%MOD + cnt[(i-1)%4])%MOD;
    	}
    	return cnt[n%4];
    }
}

执行结果

执行用时:17 ms, 在所有 Java 提交中击败了 91.58% 的用户
内存消耗:42.2 MB, 在所有 Java 提交中击败了 38.71% 的用户

执行用时:20 ms, 在所有 Java 提交中击败了 82.02% 的用户
内存消耗:35.3 MB, 在所有 Java 提交中击败了 76.05% 的用户

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值