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) 之间不存在一条可经过的路径,则没有任何一个樱桃能被摘到。
来源: https://leetcode-cn.com/problems/cherry-pickup/
法一: 自己的错误代码
思路: 要充分分析题意,原问题并不是分别求两次路径的最大值.下面代码先求最大值的路径,并记录取每个最大值路径的方向.再根据记录的方向把最大值的路径删除,再求第二个最大路径
from typing import List class Solution: def cherryPickup(self, grid: List[List[int]]) -> int: # 求行数和列数 r,c = len(grid),len(grid[0]) # 第二个数先赋值为1 grid[0][0] = (grid[0][0], grid[0][0], 0) # 处理第一列和第一行,如果存在一个-1的格子,则将其后面的都置为-1 # 并记录路径,如果是从左边来的记为1,从上边来的记为-1 # 每个tuple第一个元素记录和,第二个记录当前的原始值,第三个记录路径 for i in range(1,r): if grid[i][0] != -1: grid[i][0] = (grid[i][0]+grid[i-1][0][0], grid[i][0], -1) continue else: for j in range(i,r): grid[i][0] = (-1,-1,-1) break for i in range(1,c): if grid[0][i] != -1: grid[0][i] = (grid[0][i]+grid[0][i-1][0], grid[0][i], 1) continue else: for j in range(i,c): grid[0][i] = (-1,-1,1) break # 先求第一次路径的最大值,要记录路径 for p in range(1,r): for q in range(1,c): if grid[p][q] == -1: grid[p][q] = (-1,-1,1) # 先判断上一行的格子里面是否有荆棘 elif grid[p-1][q][0] < 0: # 如果前一列的也有荆棘,则把和赋值-1 if grid[p][q-1][0] < 0: grid[p][q] = (-1,-1,1) else: grid[p][q] = (grid[p][q-1][0]+grid[p][q], grid[p][q], 1) # 此时上一行的格子无荆棘 elif grid[p][q-1][0] < 0: # 上边和左边的格子都有荆棘则和和置为-1 grid[p][q] = (-1,-1,1) # 此时都没有荆棘 elif grid[p][q-1][0] >= grid[p-1][q][0]: # 左边的大 grid[p][q] = (grid[p][q-1][0]+grid[p][q], grid[p][q], 1) else: # 上边的大 grid[p][q] = (grid[p-1][q][0]+grid[p][q], grid[p][q], -1) answer1 = grid[-1][-1][0] print('kk',answer1) print(grid) # 根据所做的标记把第一次遍历的最大值的路径删除,即把樱桃置0 m, n = r-1, c-1 while not grid[0][0][2]: sign = grid[m][n][2] # 如果大于0说明是从左边来的,则将列减1 if sign > 0: n -= 1 grid[m][n] = (0,0,grid[m][n][2]) # 否则是从上边来的 elif sign < 0: m -= 1 grid[m][n] = (0,0,grid[m][n][2]) # 否则等于0,说明是到左上角了 else: grid[0][0] = (0,0,1) # 将最后一个格子置为0 grid[-1][-1] = (0,0,0) print(grid) # 再从左上角到右下角走一遍 for p in range(1,r): for q in range(1,c): if grid[p][q][1] == -1: pass # 如果前一列为荆棘,则上一行必不为荆棘,否则上一个if条件判断的时候已经pass掉了 elif grid[p][q-1][1] == -1: grid[p][q] = (grid[p-1][q][0]+grid[p][q][1],grid[p][q][1],grid[p][q][2]) elif grid[p-1][q][1] == -1: grid[p][q] = (grid[p][q-1][0]+grid[p][q][1],grid[p][q][1],grid[p][q][2]) else: # 注意这里一定要在最外面加括号才能保证是tuple,否则的话是int, grid[p][q] = (max(grid[p-1][q][0],grid[p][q-1][0]) + grid[p][q][1],grid[p][q][1],grid[p][q][2]) return answer1 + grid[-1][-1][0]
法二: 官方动态规划方法
思路: 典型的三维dp题,因为有三个变量,最巧妙的地方在于对边界值的处理,树的最终端的返回值只有两种情况,一个是-inf,另一个是到达(N-1,N-1)处.没完成四个分支的遍历后,记录并返回四个分支中的最大值.
class Solution(object): def cherryPickup(self, grid): N = len(grid) # 用于记录回溯函数的输入和输出值. memo = [[[None] * N for _1 in range(N)] for _2 in range(N)] def dp(r1, c1, c2): r2 = r1 + c1 - c2 # 如果遇到荆棘了,或者是从边上超出去了,则返回负无穷,相当于直接结束了这条路径 # 这里实际上是把边上围了一圈荆棘 if (N == r1 or N == r2 or N == c1 or N == c2 or grid[r1][c1] == -1 or grid[r2][c2] == -1): return float('-inf') # 如果一个点走到最后一个格子了,另一个也必定到最后一个格子了,返回值. # 如果没有这个条件,回溯的时候路径会走到(N,N-1)或(N-1,N)而返回'-inf',最终的dp也会返回-inf, # 因为-inf加上任意一个数都是-inf,有这个条件任意一条路径的终点都在(N-1,N-1)这个点上. elif r1 == c1 == N-1: # 这里会输出四个值,是因为甲乙进入最后一个格子有四种方法 print(grid[r1][c1]) return grid[r1][c1] # 如果之前遍历过这个值了则不再回溯,类似于lru_cache的功能, # 当回溯函数第二次遇到相同的输入值时,直接返回之前记录的值大大节省了时间 # 没有这个条件也能输出正确答案,只不过会超时 elif memo[r1][c1][c2] is not None: return memo[r1][c1][c2] else: # 如果c1等于c2,有r1等于r2,则两个位置重合,则樱桃只能计算一次,此时c1 != c2 为0,乘以樱桃的数量后就只计算一次 # 否则如果位置不重合,则c1 != c2为真,此时要求两条路径的和 ans = grid[r1][c1] + (c1 != c2) * grid[r2][c2] # 分四种情况进行回溯 ans += max(dp(r1, c1+1, c2+1), dp(r1+1, c1, c2+1), dp(r1, c1+1, c2), dp(r1+1, c1, c2)) # 每个四叉树回溯完都记录一次樱桃的个数,并返回,类似于576出界的路径数这道题中的方法, # 报错值的目的是方便遇到相同的输入时,直接返回值. memo[r1][c1][c2] = ans return ans dp(0,0,0) # return max(0, dp(0, 0, 0)) if __name__ == '__main__': duixiang = Solution() a = duixiang.cherryPickup( [[1,1,1,], [1,1,1,], [1,1,1,], ]) # a = duixiang.cherryPickup( [[1,1,1,1,0,0,0], # [0,0,0,1,0,0,0], # [0,0,0,1,0,0,1], # [1,0,0,1,0,0,0], # [0,0,0,1,0,0,0], # [0,0,0,1,0,0,0], # [0,0,0,1,1,1,1]] # ) print(a)
ttt