目录

LC 741. 摘樱桃 (opens new window) (opens new window)

困难

# 问题描述

一个 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。

# 记忆化搜索

可以将题意等价转换为两个人从 (0,0)(0, 0) 开始走,同时走到 (n1,n1)(n - 1, n - 1) 时能摘到的最大值,从 (0,0)(0, 0) 开始走,枚举两个人同时走的方案,若走出边界或者遇到荆棘,则返回负无穷,若两个人走到两个不同的点,则将两个点的樱桃采摘,若走到相同的点,则其中一人采摘,最终采摘方案最大的即为结果。 因为从地图中某个点出发到 (n1,n1)(n - 1, n - 1) 的最大值是固定的,所以,可以将结果缓存起来,后续方案若遇到相同的点可以直接返回。

/**
 * @param {number[][]} grid
 * @return {number}
 */
var cherryPickup = function (grid) {
  const n = grid.length
  const meno = new Map()
  const dfs = (x1, y1, x2, y2) => {
    if (x1 >= n || y1 >= n || x2 >= n || y2 >= n) {
      return -Infinity
    }
    if (grid[x1][y1] === -1 || grid[x2][y2] === -1) {
      return -Infinity
    }
    if (meno.has(`${x1}-${y1}-${x2}-${y2}`)) {
      return meno.get(`${x1}-${y1}-${x2}-${y2}`)
    }
    let cnt = 0
    if (x1 !== x2 && y1 !== y2) {
      cnt += grid[x1][y1] + grid[x2][y2]
    } else {
      cnt += grid[x1][y1]
    }
    if (x1 === n - 1 && y1 === n - 1) {
      return cnt
    }
    cnt += Math.max(
      dfs(x1 + 1, y1, x2 + 1, y2),
      dfs(x1, y1 + 1, x2, y2 + 1),
      dfs(x1 + 1, y1, x2, y2 + 1),
      dfs(x1, y1 + 1, x2 + 1, y2)
    )
    meno.set(`${x1}-${y1}-${x2}-${y2}`, cnt)
    return cnt
  }
  return Math.max(dfs(0, 0, 0, 0), 0)
}
  • 时间复杂度:O(n4)O(n^4)
  • 空间复杂度:O(n4)O(n^4)

# 动态规划

假设两人坐标为 A(x1,y1)A(x_1,y_1)B(x2,y2)B(x_2,y_2),当前走的步数为 kkkk 的取值范围是 [0,2n2][0, 2n-2]),对应每个 kk 必然有 x1+y1=x2+y2=kx_1 + y_1 = x_2 + y_2 = k,因此可以定义 dp[k][x1][x2]dp[k][x_1][x_2] 为走了 kk 步时,当前两人的坐标为 (x1,y1)(x_1,y_1)(x2,y2)(x_2,y_2) 的最大樱桃采摘数量。对于第 kk 步,可以由第 k1k - 1 步转移,因此有状态转移方程:

dp[k][x1][x2]=max{dp[k1][x1][x2]dp[k1][x11][x2]x1>0dp[k1][x1][x21]x2>0dp[k1][x11][x21]x1>0&x2>0dp[k][x_1][x_2] = max \begin{cases} dp[k-1][x_1][x_2] \\ dp[k-1][x_1 - 1][x_2] & x_1 > 0 \\ dp[k-1][x_1][x_2 - 1] & x_2 > 0 \\ dp[k-1][x_1 - 1][x_2 - 1] & x_1 > 0\;\&\;x_2 > 0 \\ \end{cases}

表示意义为:

  • dp[k1][x1][x2]dp[k-1][x_1][x_2] 表示AA向右走一步,BB向右走一步
  • dp[k1][x11][x2]dp[k-1][x_1 - 1][x_2] 表示AA向下走一步,BB向右走一步
  • dp[k1][x1][x21]dp[k-1][x_1][x_2 - 1] 表示AA向右走一步,BB向下走一步
  • dp[k1][x11][x21]dp[k-1][x_1 - 1][x_2 - 1] 表示AA向下走一步,BB向下走一步

走到下一个坐标后,若 AABB 位置都不为荆棘,则可以采摘,AA 采摘当前坐标樱桃,若 BBAA 坐标不同,则 BB 也采摘当前坐标樱桃。

/**
 * @param {number[][]} grid
 * @return {number}
 */
var cherryPickup = function (grid) {
  const n = grid.length
  const dp = new Array(2 * n - 1).fill(0).map(() => new Array(n).fill(0).map(() => new Array(n).fill(-Infinity)))
  dp[0][0][0] = grid[0][0]
  for (let k = 1; k < 2 * n - 1; k++) {
    for (let x1 = 0; x1 <= Math.min(k, n - 1); x1++) {
      for (let x2 = 0; x2 <= Math.min(k, n - 1); x2++) {
        let y1 = k - x1
        let y2 = k - x2
        if (grid[x1][y1] === -1 || grid[x2][y2] === -1) continue
        let cnt = dp[k - 1][x1][x2]
        if (x1 > 0) cnt = Math.max(cnt, dp[k - 1][x1 - 1][x2])
        if (x2 > 0) cnt = Math.max(cnt, dp[k - 1][x1][x2 - 1])
        if (x1 > 0 && x2 > 0) cnt = Math.max(cnt, dp[k - 1][x1 - 1][x2 - 1])
        cnt += grid[x1][y1]
        if (x1 !== x2) cnt += grid[x2][y2]
        dp[k][x1][x2] = cnt
      }
    }
  }
  return Math.max(dp[2 * n - 2][n - 1][n - 1], 0)
}
  • 时间复杂度:O(n3)O(n^3)
  • 空间复杂度:O(n3)O(n^3)
上次更新: 2023/01/31 19:48:05

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 , 转载请注明出处!