返回

从 LeetCode 741 摘樱桃学经典线性 DP 应用题

后端

从 LeetCode 741 摘樱桃学经典线性 DP 应用题

前言

在 LeetCode 的浩瀚题海中,有许多经典题目可以帮助我们掌握各种编程算法和数据结构。其中,线性动态规划(DP)是解决许多问题的重要技巧。LeetCode 741 摘樱桃就是一道经典的线性 DP 题目。在本篇文章中,我们将详细解析这道题目,一步步带领您理解算法思想和实现步骤,并提供详细的示例和清晰的讲解,帮助您深入理解线性动态规划的应用。

题目

在一个 N \times N 的网格中,每个格子代表了一块樱桃地。每个格子由以下三种数字之一组成:

  • 0 表示一个空地
  • 1 表示一棵樱桃树
  • 2 表示一个障碍物

您从网格的左上角出发,每次只能向右或向下走一步。您想收集尽可能多的樱桃,但不能经过障碍物。

算法思想

解决这道题目的关键在于理解线性动态规划的思想。线性动态规划是一种自底向上的算法,它将问题分解成一系列子问题,然后逐步解决这些子问题,最终得到问题的整体解决方案。

对于这道题目,我们可以将问题分解成以下几个子问题:

  • 当您到达网格的某个格子时,您可以选择向右或向下走一步。
  • 每次移动,您都可以收集该格子中的樱桃。
  • 您不能经过障碍物。

那么,对于每个格子,我们都可以计算出从该格子出发能够收集到的最大樱桃数量。这个数量取决于以下几个因素:

  • 该格子本身的樱桃数量
  • 从该格子向右走一步所能收集到的最大樱桃数量
  • 从该格子向下走一步所能收集到的最大樱桃数量

我们只需要找到一个公式,将这些因素结合起来,就可以计算出每个格子的最大樱桃数量。

算法实现

根据上述算法思想,我们可以将算法实现如下:

def cherry_picking(grid):
  """
  :type grid: List[List[int]]
  :rtype: int
  """
  n = len(grid)
  dp = [[[-1 for _ in range(3)] for _ in range(n)] for _ in range(n)]

  def dfs(x, y, state):
    """
    :type x: int
    :type y: int
    :type state: int
    :rtype: int
    """
    if x == n - 1 and y == n - 1:
      return 0

    if dp[x][y][state] != -1:
      return dp[x][y][state]

    res = 0
    if state == 0:
      res = max(dfs(x + 1, y, 1), dfs(x, y + 1, 2))
    elif state == 1:
      res = grid[x][y] + max(dfs(x + 1, y, 1), dfs(x, y + 1, 0))
    else:
      res = grid[x][y] + max(dfs(x + 1, y, 2), dfs(x, y + 1, 1))

    dp[x][y][state] = res
    return res

  return dfs(0, 0, 0)

算法分析

算法的时间复杂度为 O(3n^2),其中 n 是网格的大小。这是因为对于每个格子,我们需要计算三种不同的状态,因此算法需要遍历网格中的每个格子三次。

算法的空间复杂度也为 O(3n^2),这是因为我们需要存储每个格子的三种不同状态。

结语

通过 LeetCode 741 摘樱桃这道经典题目,我们深入理解了线性动态规划的思想和应用。我们还详细解析了算法的实现细节,并提供了详细的示例和清晰的讲解。希望这篇文章能够帮助您掌握线性动态规划这一重要算法技巧,并将其应用到更多的问题中去。