LC每日一题|20240506 - 741. 摘樱桃
给你一个
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)之间不存在一条可经过的路径,则无法摘到任何一个樱桃。
提示:
n == grid.lengthn == grid[i].length1 <= n <= 50grid[i][j]为-1、0或1grid[0][0] != -1grid[n - 1][n - 1] != -1
题目等级:Hard
解题思路
确实是很难的一道题,抄题解了~
最开始太想当然了,想到的方案是从(0, 0)BFS到(n - 1, n - 1),然后DFS标记出去程的最大效益路径,再BFS回来。
以下是错误代码
class Solution {
fun cherryPickup(grid: Array<IntArray>): Int {
val c = Array<IntArray>(grid.size) { IntArray(grid[0].size) }
if (grid[0][0] == 1) c[0][0] = 1
val queue = ArrayDeque<IntArray>()
queue.addLast(intArrayOf(0, 0))
while (queue.isNotEmpty()) {
repeat(queue.size) {
val node = queue.removeFirst()
val i = node[0]
val j = node[1]
if (i < grid.size - 1 && grid[i + 1][j] != -1) {
queue.addLast(intArrayOf(i + 1, j))
c[i + 1][j] = Math.max(c[i + 1][j], c[i][j] + if (grid[i + 1][j] == 0) 0 else 1)
}
if (j < grid[0].size - 1 && grid[i][j + 1] != -1) {
queue.addLast(intArrayOf(i, j + 1))
c[i][j + 1] = Math.max(c[i][j + 1], c[i][j] + if (grid[i][j + 1] == 0) 0 else 1)
}
}
}
if (c.last().last() == 0) return 0
val first = c.last().last()
grid[grid.size - 1][grid.size - 1] = 0
fun dfs(i: Int, j: Int, cur: Int) {
if (i == 0 && j == 0) return
if (i > 0 && c[i][j] - c[i - 1][j] <= 1) {
grid[i - 1][j] = 0
dfs(i - 1, j, c[i - 1][j])
} else if (j > 0 && c[i][j] - c[i][j - 1] <= 1) {
grid[i][j - 1] = 0
dfs(i, j - 1, c[i][j - 1])
}
}
dfs(grid.size - 1, grid.size - 1, c.last().last())
for (i in c.indices) for (j in c.indices) c[i][j] = 0
queue.addLast(intArrayOf(grid.size - 1, grid.size - 1))
while (queue.isNotEmpty()) {
repeat(queue.size) {
val node = queue.removeFirst()
val i = node[0]
val j = node[1]
if (i > 0 && grid[i - 1][j] != -1) {
queue.addLast(intArrayOf(i - 1, j))
c[i - 1][j] = Math.max(c[i - 1][j], c[i][j] + if (grid[i - 1][j] == 0) 0 else 1)
}
if (j > 0 && grid[i][j - 1] != -1) {
queue.addLast(intArrayOf(i, j - 1))
c[i][j - 1] = Math.max(c[i][j - 1], c[i][j] + if (grid[i][j - 1] == 0) 0 else 1)
}
}
}
return first + c[0][0]
}
}
在如下的case中翻车了。很明显,单次最优并不代表全局最优。
题解的思路其实也不算复杂。
由于回程实际上是去程的逆操作,所以我们可以将去程与回程看作两个同时发生的去程。而因为题目限制了移动方向,所以无论是走哪一条路,到达终点所走的步数肯定都是一样的。而由于一个维度的步数等于总步数减去另一个维度的步数,所以可以只记录其中的一个维度。
所以在某个时间点,忽略走到边缘的情况,则其最优值应该由4种情况转移而来,即两个去程分别向右走和向下走。如果两个去程发生碰撞,则只记录一次摘取。
AC代码
class Solution {
fun cherryPickup(grid: Array<IntArray>): Int {
val dp = Array<Array<IntArray>>(grid.size * 2 - 1) { Array<IntArray>(grid.size) { IntArray(grid.size) { Int.MIN_VALUE } } }
dp[0][0][0] = grid[0][0]
for (k in 1 until grid.size * 2 - 1) {
for (x1 in Math.max(k - grid.size + 1, 0) .. Math.min(k, grid.size - 1)) {
if (grid[x1][k - x1] == -1) continue
for (x2 in x1 .. Math.min(k, grid.size - 1)) {
if (grid[x2][k - x2] == -1) continue
var res = dp[k - 1][x1][x2]
if (x1 > 0) res = Math.max(res, dp[k - 1][x1 - 1][x2])
if (x2 > 0) res = Math.max(res, dp[k - 1][x1][x2 - 1])
if (x1 > 0 && x2 > 0) res = Math.max(res, dp[k - 1][x1 - 1][x2 - 1])
res += grid[x1][k - x1]
if (x1 != x2) res += grid[x2][k - x2]
dp[k][x1][x2] = res
}
}
}
if (dp.last().last().last() > 0) return dp.last().last().last()
return 0
}
}
时间复杂度:O(n^3)
空间复杂度:O(n^3)
由于k一定是由k - 1转移而来,所以可以进行空间优化,由后向前。
class Solution {
fun cherryPickup(grid: Array<IntArray>): Int {
val dp = Array<IntArray>(grid.size) { IntArray(grid.size) { Int.MIN_VALUE } }
dp[0][0] = grid[0][0]
for (k in 1 until grid.size * 2 - 1) {
for (x1 in Math.min(k, grid.size - 1) downTo Math.max(k - grid.size + 1, 0)) {
for (x2 in Math.min(k, grid.size - 1) downTo x1) {
if (grid[x1][k - x1] == -1 || grid[x2][k - x2] == -1) {
dp[x1][x2] = Int.MIN_VALUE
continue
}
var res = dp[x1][x2]
if (x1 > 0) res = Math.max(res, dp[x1 - 1][x2])
if (x2 > 0) res = Math.max(res, dp[x1][x2 - 1])
if (x1 > 0 && x2 > 0) res = Math.max(res, dp[x1 - 1][x2 - 1])
res += grid[x1][k - x1]
if (x1 != x2) res += grid[x2][k - x2]
dp[x1][x2] = res
}
}
}
if (dp.last().last() > 0) return dp.last().last()
return 0
}
}
时间复杂度:O(n^3)
空间复杂度:O(n^2)