【LeetCode】摘樱桃 [H](记忆化搜索)

166 篇文章 0 订阅
这篇博客介绍了一个在网格中采摘樱桃的动态规划算法。玩家从左上角出发,只能向下或向右走,最终返回起点,途中尽可能多地采摘樱桃。题目中给出了一种递归的解题思路,通过一个三维数组dp来存储状态,并优化了参数以提高效率。当遇到荆棘或无法到达终点的情况时,返回系统最小值表示无法采摘。最终返回的是能采摘到的樱桃总数。
摘要由CSDN通过智能技术生成

741. 摘樱桃 - 力扣(LeetCode)

一、题目

一个N x N的网格(grid) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示:

0 表示这个格子是空的,所以你可以穿过它。
1 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。
-1 表示这个格子里有荆棘,挡着你的路。
你的任务是在遵守下列规则的情况下,尽可能的摘到最多樱桃:

从位置 (0, 0) 出发,最后到达 (N-1, N-1) ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为0或者1的格子);
当到达 (N-1, N-1) 后,你要继续走,直到返回到 (0, 0) ,只能向上或向左走,并且只能穿越有效的格子;
当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为0);
如果在 (0, 0) 和 (N-1, N-1) 之间不存在一条可经过的路径,则没有任何一个樱桃能被摘到。

示例 1:
输入: grid =
[[0, 1, -1],
 [1, 0, -1],
 [1, 1,  1]]
输出: 5
解释: 
玩家从(0,0)点出发,经过了向下走,向下走,向右走,向右走,到达了点(2, 2)。
在这趟单程中,总共摘到了4颗樱桃,矩阵变成了[[0,1,-1],[0,0,-1],[0,0,0]]。
接着,这名玩家向左走,向上走,向上走,向左走,返回了起始点,又摘到了1颗樱桃。
在旅程中,总共摘到了5颗樱桃,这是可以摘到的最大值了。

提示:

  • grid 是一个 N * N 的二维数组,N的取值范围是1 <= N <= 50。
  • 每一个 grid[i][j] 都是集合 {-1, 0, 1}其中的一个数。
  • 可以保证起点 grid[0][0] 和终点 grid[N-1][N-1] 的值都不会是 -1。

二、代码

class Solution {
    public static int cherryPickup(int[][] grid) {
        // 矩阵的行数
        int n = grid.length;
        // 矩阵的列数
        int m = grid[0].length;
        // dp缓存
        // dp[i][j][k]:表示A来到(i,j),B来到(k,i + j - k)时,能收集到的最多樱桃数量
        int[][][] dp = new int[n][m][n];
        // 先给dp缓存赋初值,这里设置为系统最小值,用来表示当前位置的值还从来没有计算过
        // 其实只要是赋值小于-1的数用来标记该位置的值还没有计算过就可以,因为题目中有意义的值都是大于等于-1的
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                for (int k = 0; k < n; k++) {
                    dp[i][j][k] = Integer.MIN_VALUE;
                }
            }
        }
       
        // 开始递归,A和B同时从左上角出发
        int ans =  process(0, 0, 0, grid, n, m, dp);
        // 如果返回-1,就说明根本无法从起点走到终点再走回去,这种情况就认为能拿到0个樱桃
        return ans == -1 ? 0 : ans;
    }

    // 此时A来到(ai,aj),B来到(bi,ai + aj - bi),求此时已经能拿到的最大樱桃数。返回-1表示根本无法从起点走到终点再走回去
    // 这里只用了三个可变参数表示A和B的坐标,因为A和B都是只能向下或者向右走,并且他们是同步移动的,所以他们两个走的步数一定一直都是一样的,所以只要是知道了三个坐标数,就可以推出来下一个
    public static int process(int ai, int aj, int bi, int[][] grid, int n, int m, int[][][] dp) {
        // 省一个参数,因为A和B是同步走的,所以他们走过的步数一定是一样的,又因为他们只能向下走或者向右走,所以他们走的步数就是纵坐标+横坐标。
        // 所以他们的坐标满足a+b=c+d的关系,也就是说只要是知道了三个数,就可以推出第四个数,这就省掉了一个可变参数,提高了效率。
        // 计算B的列下标
        int bj = ai + aj - bi;

        // 判断此时位置是否越界,如果越界直接返回系统最小值,表示这个位置根本就是无效的,也就不用为其赋值
        if (ai >= n || bi >= n || aj >= m || bj >= m) {
            return Integer.MIN_VALUE;
        }

        // 如果该位置在缓存中已经有了结果了,直接取出来返回
        if (dp[ai][aj][bi] != Integer.MIN_VALUE) {
            return dp[ai][aj][bi];
        }

        // 如果此时两个点已经到达了右下角重点,此时两个点一定是同时到达的,所以只能取一次樱桃,将这个位置的樱桃树复制到dp,并返回
        // 其实这里只判断ai == n - 1 && aj == m - 1即可,因为A和B一定能同时到达终点
        if (ai == n - 1 && aj == m - 1 && bi == n - 1 && bj == m - 1) {
            dp[ai][aj][bi] = grid[ai][aj];
            return dp[ai][aj][bi];
        }

        // 尝试全部有可能的四种移动方式,通过递归得到这几种方式能拿到的最大樱桃树
        int p1 = process(ai + 1, aj, bi + 1, grid, n, m, dp);
        int p2 = process(ai, aj + 1, bi, grid, n, m, dp);
        int p3 = process(ai + 1, aj, bi, grid, n, m, dp);
        int p4 = process(ai, aj + 1, bi + 1, grid, n, m, dp);

        // 从这四种结果中取最大值,作为后续的走法能拿到的最大樱桃树
        int next = Math.max(p1, Math.max(p2, Math.max(p3, p4)));

        // 记录当前已经能拿到的最大樱桃树
        int cur = 0;
        // 如果此时A、B所在位置或者后续返回上来的数有一个是-1,就说明此时这个走法一定是走不通的,不可能走到终点,那么就将该位置也设置为-1,返回
        if (grid[ai][aj] == -1 || grid[bi][bj] == -1 || next == -1) {
            // 上面三个数存在-1,要么表示当前位置会被-1挡住(也就是去或者回的路会被当前遇到的-1挡住),要么表示后续走过的路会被-1挡住,不管哪一种情况,都意味着当前这个走法是走不通的
            dp[ai][aj][bi] = -1;
            return dp[ai][aj][bi];
        // 走到这个分支,表示当前的路线能走通
        } else {
            // 如果此时A和B的位置不同,说明两个位置的樱桃出都要累加到当前能拿到的樱桃数中
            if (ai != bi || aj != bj) {
                cur = grid[ai][aj] + grid[bi][bj];
            // 如果A和B此时在同一位置,那么就只能拿一次樱桃
            } else {
                cur = grid[ai][aj];
            }
        }

        // 将当前能拿到的最大樱桃数量加上后续步骤能拿到的最大樱桃树两
        dp[ai][aj][bi] = cur + next;
        return dp[ai][aj][bi];
    }

}

三、解题思路 

我们就假设两个人A、B都从左下角走到右下角,都只能向下或者向右走,但是A和B能做出不同的选择

如果,某一时刻,AB进入相同的一个格子,A和B只获得一份(如果A和B都要通过同一个格子,他们必定同时到达这个格子的,因为他们起点相同,又只能走下或右,所以一定会同时进入到相同的格子),A走到之后,就认为B走过的路径就是回来的路径。

能够省一个参数,因为A和B是同步走的,所以他们走过的步数一定是一样的,又因为他们只能向下走或者向右走,所以他们走的步数就是纵坐标+横坐标。

所以他们的坐标满足a+b=c+d的关系,也就是说只要是知道了三个数,就可以推出第四个数,这就省掉了一个可变参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值