
链接:老鼠跳跃
题解:老鼠跳跃
状态定义
-
dp[i][0]:到达第i个台阶时,已经进行了偶数次跳跃的方案数。 -
dp[i][1]:到达第i个台阶时,已经进行了奇数次跳跃的方案数。 -
初始:
dp[0][0] = 1,表示在起点(顶部)还未跳跃,跳跃次数为 0(偶数)。
跳跃规则
-
偶数次跳跃 (第 2、4、6...次):可跳
1, 3, 4个台阶 → 对应even = {1, 3, 4}。 -
奇数次跳跃 (第 1、3、5...次):可跳
1, 2, 4个台阶 → 对应odd = {1, 2, 4}。
状态转移
遍历每个位置 i(从 0 到 n-2):
-
如果
arr[i] == 1(有胶水),则不能停在这里,跳过。 -
否则,从更小的源位置
i - jump转移过来:-
若源状态是偶数(
dp[i - odd[j]][0]),下一步是奇数次跳跃,使用odd中的步数,到达后状态变为奇数(dp[i][1])。 -
若源状态是奇数(
dp[i - even[j]][1]),下一步是偶数次跳跃,使用even中的步数,到达后状态变为偶数(dp[i][0])。
-
统计答案
地面是索引 n-1。从某个位置 i 出发,如果 i + jump >= n-1,则成功到达地面或越过地面。
-
对于偶数状态
dp[i][0],下一步是奇数次跳跃,使用odd中的步数。 -
对于奇数状态
dp[i][1],下一步是偶数次跳跃,使用even中的步数。代码中对于每个
jump,遍历满足i >= n-1 - jump且i < n-1的i,累加对应的dp[i][状态]。
为什么代码是正确的?
-
方向正确 :从高处(小索引)往低处(大索引)跳,索引增加,状态转移从
i - jump到i,符合物理方向。 -
胶水处理 :
if (arr[i] == 1) continue;确保不会停在有胶水的台阶上,也不会从有胶水的台阶继续跳(因为dp[i]保持为 0)。 -
奇偶状态正确:根据当前跳跃次数的奇偶性选择正确的步数集合。
-
地面及以下处理 :统计时只要
i + jump >= n-1就计入,不同的(i, jump)组合对应不同的路径,因此方案数正确。 -
取模:每次加法后取模,避免溢出。
样例验证
-
样例1
[0,0,0]:输出5,与代码计算结果一致。 -
样例2
[0,0,1,0]:输出3,与代码计算结果一致。
cpp
class Solution {
public:
/**
* @param arr: the steps whether have glue
* @return: the sum of the answers
*/
int ratJump(vector<int> &arr) {
int n = arr.size();
if (n <= 0) {
return 0;
}
long long MOD = 1000000007;
vector<vector<int>> dp(n, vector<int>(2, 0));
// 0 表示到达当前为偶数步骤
// 1 表示到达当前为奇数步骤
dp[0][0] = 1;
vector<int> even{1, 3, 4}; // 当前正进行偶数次可以走到步数
vector<int> odd{1, 2, 4}; // 当前正进行奇数次可以走到步数
//
for (int i = 0; i < n-1; ++i) {
if (arr[i] == 1) {
continue;
}
for (int j = 0; j < 3; ++j) {
if (i - odd[j] >= 0) {
dp[i][1] = (dp[i][1] + dp[i - odd[j]][0]) % MOD;
}
if (i - even[j] >= 0) {
dp[i][0] = (dp[i][0] + dp[i - even[j]][1]) % MOD;
}
}
}
int result = 0;
for (auto jump : odd) {
// 从i位置开始到[i, n-1)这些位置,经过jump步骤都可以跳到n-1以及外面的位置
for (int i = max(0, n - 1 - jump); i < n-1; ++i) {
result = (result + dp[i][0]) % MOD;
}
}
for (auto jump : even) {
for (int i = max(0, n - 1 - jump); i < n-1; ++i) {
result = (result + dp[i][1]) % MOD;
}
}
return result;
}
}
算法 DP动态规划
DP方程
dpi0/1表示奇数次/偶数次跳到i位置的方案数
0代表even偶数
1代表odd奇数
由于题目的arr是从高到低给出的,那么dp0就是我们的最高层
for i 0:n
这种枚举方式就是从高层到底层
DP边界条件
dp00=1
偶数次跳到了0位置,那么下次就是奇数次(也就是第一次)
DP转移
如果奇数次跳到了i,那么i再往下跳应该是偶数次
如果偶数次跳到了i,那么i再往下跳应该是奇数次
奇数次可以跳1,2,4 偶数次可以跳1,3,4
dpi0去更新dpi+odd1
dpi1去更新dpi+even0
伪代码中i只用枚举到n-2,不枚举到n-1的原因是 n-1已经是地面了 不能再往下跳
// 本题解信息来源于【九章算法】。请勿进行商业转载,非商业转载请注明出处。
//枚举状态dp[i][0] dp[i][1]
for(int i = 0; i < n - 1; i++) {
//枚举转移,看这一步跳几个台阶
for(int j = 0; j < 3; j++) {
//1能直接跳下来,若超过地面,也算是可以到达
//2不能直接跳下来的话 要跳到没有胶水的地方
if(i + odd[j] >= n || arr[i + odd[j]] == 0) {
dp[i + odd[j]][1] += dp[i][0];
dp[i + odd[j]][1] %= MOD;
}
if(i + even[j] >= n || arr[i + even[j]] == 0) {
dp[i + even[j]][0] += dp[i][1];
dp[i + even[j]][0] %= MOD;
}
}
}
注意事项
题目表示是可以跳到地面之下的,也就是我们更新的时候i+odd或者i+even超过了层数也是合法的转移
统计答案时候也应该把这一部分统计进去
// 本题解信息来源于【九章算法】。请勿进行商业转载,非商业转载请注明出处。
//枚举统计跳到地面或者地面再往下的答案
long long ans = 0;
for(int i = n - 1; i < n + 4; i++) {
ans = (ans + dp[i][0] + dp[i][1]) % MOD;
}
复杂度分析
时间复杂度
dp状态量是O(N) 转移是O(1)**** 所以时间复杂度是O(N)
空间复杂度
dp数组是n*2的,所以空间复杂度O(N)
cpp
// 本题解信息来源于【九章算法】。请勿进行商业转载,非商业转载请注明出处。
class Solution {
public:
/**
* @param arr: the steps whether have glue
* @return: the sum of the answers
*/
int ratJump(vector<int> &arr) {
// Write your code here.
const long long MOD = 1000000007;
//楼梯有无胶水的状态是从高往低输入的
//arr长度
int n = arr.size();
//初始化dp数组
//dp[i][0] even
//dp[i][1] odd
vector<vector<long long>>dp(n + 10, vector<long long>(2, 0));
//边界条件
dp[0][0] = 1;
//奇数偶数次分别能跳的数量
int odd[] = {1, 2, 4};
int even[] = {1, 3, 4};
//枚举状态dp[i][0] dp[i][1]
for(int i = 0; i < n - 1; i++) {
//枚举转移,看这一步跳几个台阶
for(int j = 0; j < 3; j++) {
//1能直接跳下来,若超过地面,也算是可以到达
//2不能直接跳下来的话 要跳到没有胶水的地方
if(i + odd[j] >= n || arr[i + odd[j]] == 0) {
dp[i + odd[j]][1] += dp[i][0];
dp[i + odd[j]][1] %= MOD;
}
if(i + even[j] >= n || arr[i + even[j]] == 0) {
dp[i + even[j]][0] += dp[i][1];
dp[i + even[j]][0] %= MOD;
}
}
}
//枚举统计跳到地面或者地面再往下的答案
long long ans = 0;
for(int i = n - 1; i < n + 4; i++) {
ans = (ans + dp[i][0] + dp[i][1]) % MOD;
}
return (int)ans;
}
};