[H记忆化搜索] lc403. 青蛙过河(记忆化搜索+dp+经典宽搜bfs)

12 篇文章 0 订阅
2 篇文章 0 订阅

1. 题目来源

链接:403. 青蛙过河

相关题目:[记忆化搜索] 滑雪(模板题+记忆化搜索)

2. 题目解析

很不错的一道 dp 问题,但是普通的 二维 dp 在此会 TLE,故采用记忆化搜索来优化常数。也是一道很不错的记忆化搜索练习题目!

思路:

  • 状态表示f[i][j] 表示跳到i 个点, 且从第 i 个点往后跳的长度可以为 j 的所有方案的集合。f[i][j] = ture/false。在该种状态表示下,答案即为 f[n-1][0]~f[n-1][n] 中只要有一个为 true 则为 true,否则为 false
  • 状态转移f[i][j] 下,上一个跳过来的步长可以为 j-1、j、j+1 三种,当这三种中的任意一种能够之前的路径能够成立,那么就能保证 f[i][j]=true,则完成状态转移。考虑长度为 j-1 的这个情况。即要跳到 i 点,且长度是 j-1,则意味着上一个点一定是 f[i-(j-1)][j-1] 这个情况转移来的,那么需要保证 f[i-(j-1)][j-1]=true 且保证 i-(j-1) 这个位置是石头。当这两个情况同时满足时,f[i][j] 即为 true
  • 初始化f[0][1]=true,因为规定第一个石头只能跳一步,则位于第 0 号石头,且可以往后跳一步的方案是合法的,故为 true
  • 优化:哈希表将坐标映射到节点编号,用以快速判断上个点是否为石头。状态转移是 O(1) 的,状态个数是 O(n*n) 的,最坏情况下依旧是 O ( n 2 ) O(n^2) O(n2) 的,但是由于中间过程中剪枝的存在可以优化一些常数,会比循环快一点。

dp 有两种实现方式,循环、记忆化搜索。这两种可以互相转换,都可以实现 dp。且记忆化搜索更为简单,代码量更少,思维形式更符合 dp 的转移方程。

官方题解和下面的宽搜记忆化都很不凑。


  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度 O ( n 2 ) O(n^2) O(n2)

代码:

详细注释:

const int N = 2005;

int f[N][N];

class Solution {
public:
    vector<int> p;
    unordered_map<int, int> h;

    int dp(int i, int j) {
        if (f[i][j] != -1) return f[i][j];      // 被搜过了,就直接返回当前值即可   

        f[i][j] = 0;                            // 赋初始值为 0,表示搜索过了,当前不可达
        for (int l = max(1, j - 1); l <= j + 1; l ++ ) {    // 最少跳一步,最多跳 j+1 步
            int x = p[i] - l;                   // 计算 i 这个石头的上个石头的下标
            if (h.count(x)) {                   // 判断上个石头是否存在
                int k = h[x];                   // 如果上个石头存在,得到上个石头的下标
                if (dp(k, l)) {                 // 判断上个石头状态是否为 true,在此其跳出的距离一定是 l
                    f[i][j] = 1;                // 为 true,则表示当前 i 石头跳出为 j 步的状态是可行的
                    break;                     
                }
            }
        }
        return f[i][j];
    }

    // f[i][j] 表示跳到了 i 这个点上,且从 i 这个点往后跳 j 步的所有方案的集合
    bool canCross(vector<int>& stones) {
        p = stones;
        int n = stones.size();
        for (int i = 0; i < n; i ++ ) h[stones[i]] = i;

        memset(f, -1, sizeof f);        // -1 作为无效值,表示状态未被更新,0 表示状态更新了,但是不能跳过来,1 表示能跳过来

        f[0][1] = 1;                    // 初始化只能跳一步
        for (int i = 0; i < n; i ++ )   // f[n-1][j:0~n] 只要有一个是能跳过来的,即状态为 1 就表示 n 号点能达 
            if (dp(n - 1, i))           // 记忆化搜索
                return true;
        return false;
    }
};

期间看到一个记忆化宽搜 bfs 的代码感觉也很不错,实际上和本题的记忆化搜索思路上是一致的。感觉一个是从后向前、一个是从前向后。题解链接。
在这里插入图片描述

class Solution {
public:
    bool canCross(vector<int>& stones) {
        int n = stones.size();

        unordered_map<int, int> hmp;
        for (int i = 0; i < n; i ++ ) hmp[stones[i]] = i;

        vector<vector<bool>> f(n, vector<bool>(n + 1, false));

        queue<pair<int, int>> q;
        q.push(make_pair(0, 0));

        while (q.size()) {
            auto t = q.front();
            q.pop();
            int cur_x = t.first, cur_k = t.second;
            
            if (cur_x == n - 1) return true;

            for (int i = - 1; i <= 1; i ++ ) {
                if (cur_k + i <= 0) continue;

                int next_k = cur_k + i;
                int next_p = stones[cur_x] + next_k;
                if (hmp.count(next_p)) {
                    int next_x = hmp[next_p];
                    if (!f[next_x][next_k]) {
                        f[next_x][next_k] = true;
                        q.push(make_pair(next_x, next_k));
                    }
                }
            }
        } 
        return false;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值