1861 · 老鼠跳跃(坐标型dp)

链接:老鼠跳跃

题解:老鼠跳跃

状态定义

  • 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][状态]。

为什么代码是正确的?

  1. 方向正确 :从高处(小索引)往低处(大索引)跳,索引增加,状态转移从 i - jump 到 i,符合物理方向。

  2. 胶水处理 :if (arr[i] == 1) continue; 确保不会停在有胶水的台阶上,也不会从有胶水的台阶继续跳(因为 dp[i] 保持为 0)。

  3. 奇偶状态正确:根据当前跳跃次数的奇偶性选择正确的步数集合。

  4. 地面及以下处理 :统计时只要 i + jump >= n-1 就计入,不同的 (i, jump) 组合对应不同的路径,因此方案数正确。

  5. 取模:每次加法后取模,避免溢出。

样例验证

  • 样例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;
    }
};
相关推荐
Lazionr1 小时前
手撕 unordered_set 和 unordered_map,剖析哈希表底层实现
c++·算法
0+1112 小时前
算法 --归并排序
数据结构·算法·排序算法
xie0510_2 小时前
Any类简要实现
开发语言·c++·算法
字节跳动的猫2 小时前
LikeShop 种草社区模块二开:笔记发布、话题广场与商品挂载实现
数据结构·数据库
道尔柯南2 小时前
C++ 入门基础详解:从第一个程序到核心语法特性
开发语言·c++
一棵树73512 小时前
数据结构闲聊
数据结构·算法
bkspiderx2 小时前
在 MFC 中添加自定义消息:从定义到使用的完整指南
c++·mfc·postmessage·sendmessage·mfc 中添加自定义消息·wm_app·on_message
bro_Java6662 小时前
《二叉树的基础知识、操作方法与代码示例》
java·数据结构·算法·广度优先
纪念 2292 小时前
C++ string(三)
android·c++