题目描述
三步问题。有个小孩正在上楼梯,楼梯有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(n−1)+f(n−2)+f(n−3)
那么通过:
[
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(n−1)f(n−2)f(n−3)]∗⎣⎡a1a2a3b1b2b3c1c2c3⎦⎤=[f(n)f(n−1)f(n−2)]
得到:
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(n−1)+a2f(n−2)+a3f(n−3)f(n−1)=b1f(n−1)+b2f(n−2)+b3f(n−3)f(n−2)=c1f(n−1)+c2f(n−2)+c3f(n−3)
可知:
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% 的用户