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]
View Code

法二: 官方动态规划方法

思路: 典型的三维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)
View Code

ttt

posted on 2019-12-31 12:36  吃我一枪  阅读(283)  评论(0编辑  收藏  举报

导航