返回

揭秘"方向"视角下的树形DP——剖析LeetCode 834难题

后端

树形 DP 的方向视角:纵览树中距离之和的求解之道

简介

树形 DP,作为算法竞赛和实际编程中的利器,以其严谨的推导和清晰的递归关系,成为解决树形结构问题的有力工具。今天,我们将深入探讨树形 DP 的"方向"视角,以 LeetCode 上的经典难题——834. 树中距离之和为例,揭开树形 DP 的神秘面纱,助力你算法技能的提升。

什么是"方向"视角?

"方向"视角是一种将树形结构问题分解为一系列子问题的思维方式。通过明确子问题的定义及其相互依赖关系,我们可以逐步解决整个问题,如同沿着树的枝干逐层深入一般。

834. 树中距离之和

题目

这是一道困难级别的 LeetCode 题目,标签包括"树形 DP"、"DFS"、"动态规划"和"树"。

题目要求我们计算一棵树中所有节点对 (i, j) 的距离之和,其中 i != j。树中节点的编号从 0 到 n-1,根节点为 0。同时,题目给出了一个整数数组 dist,其中 dist[i] 表示节点 i 到根节点 0 的距离。

解决方案:树形 DP 的"方向"视角

运用树形 DP 的"方向"视角,我们将子问题定义为:f(i, j) 表示以 i 为根节点的子树中,节点 i 到节点 j 的距离之和。

子问题之间的依赖关系:

对于每个节点 i,其子树的距离之和可以分解为两部分:

  • 以 i 为根节点的子树中,节点 i 到其子节点的距离之和。
  • 以 i 为根节点的子树中,节点 i 的子节点到其子节点的距离之和。

树形 DP 的递归关系:

根据子问题的定义和依赖关系,我们可以得到以下递归关系:

f(i, j) = sum(f(son, j)) + sum(dist[son]) * (depth[son] + 1)

其中,son 表示 i 的子节点,depth[son] 表示 son 的深度,即 son 到根节点 0 的距离。

LeetCode 834 的求解步骤:

  1. 初始化: 将 f(i, j) 数组初始化为 0。
  2. 递归计算: 使用深度优先搜索 (DFS) 算法遍历树,并根据递归关系计算每个子问题的答案。
  3. 累加结果: 将所有子问题的答案累加起来,得到最终结果。

代码实现:

def tree_dp(n, dist, adj):
  """
  计算树中所有节点对(i, j)的距离之和,其中i != j。

  参数:
    n:树的节点个数
    dist:节点到根节点的距离数组
    adj:邻接表

  返回:
    树中所有节点对的距离之和
  """

  # 初始化f(i, j)数组
  f = [[0 for _ in range(n)] for _ in range(n)]

  # 递归计算每个子问题的答案
  def dfs(i, parent):
    # 计算以i为根节点的子树中,节点i到其子节点的距离之和
    sum_i_to_son = 0
    for son in adj[i]:
      if son != parent:
        dfs(son, i)
        sum_i_to_son += f[son][j] + dist[son] * (depth[son] + 1)

    # 计算以i为根节点的子树中,节点i的子节点到其子节点的距离之和
    sum_son_to_son = 0
    for son1 in adj[i]:
      if son1 != parent:
        for son2 in adj[i]:
          if son2 != son1 and son2 != parent:
            sum_son_to_son += f[son1][son2]

    # 更新f(i, j)数组
    f[i][j] = sum_i_to_son + sum_son_to_son

  # 计算每个节点到根节点的深度
  depth = [0 for _ in range(n)]
  def dfs_depth(i, parent, d):
    depth[i] = d
    for son in adj[i]:
      if son != parent:
        dfs_depth(son, i, d + 1)

  dfs_depth(0, -1, 0)

  # 累加所有子问题的答案
  total_sum = 0
  for i in range(n):
    for j in range(n):
      if i != j:
        total_sum += f[i][j]

  return total_sum

# 测试代码
n = 5
dist = [0, 1, 2, 3, 4]
adj = [[] for _ in range(n)]
adj[0].append(1)
adj[0].append(2)
adj[1].append(0)
adj[1].append(3)
adj[2].append(0)
adj[2].append(4)
adj[3].append(1)
adj[4].append(2)

result = tree_dp(n, dist, adj)
print(result)

常见问题解答

  1. 什么是树形 DP?

    树形 DP 是一种解决树形结构问题的动态规划算法,其特点是将问题分解为子问题,并利用树的层级结构递推求解。

  2. "方向"视角在树形 DP 中的作用是什么?

    "方向"视角帮助我们从树的根节点出发,逐层向下递归,逐步求解每个子问题的答案,如同沿着树的枝干逐层深入一般。

  3. LeetCode 834 题目的难点在哪里?

    本题目的难点在于需要同时考虑节点到根节点的距离和节点之间的距离,并将其综合考虑在子问题的递归关系中。

  4. 树形 DP 的应用场景有哪些?

    树形 DP 可用于解决各种树形结构问题,例如:树上最长路径、树上距离、树上背包和树上差分等。

  5. 如何提高树形 DP 的解题能力?

    熟练掌握树形 DP 的基础概念和递归关系,多练习不同类型的树形 DP 题目,逐步提高自己的解题能力。

结论

树形 DP 的"方向"视角为我们提供了一种清晰的思路,帮助我们分层求解树形结构问题。通过理解和运用这一视角,我们可以解锁更多算法难题,提升自己的编程技能。希望这篇文章能够为你的树形 DP 之旅提供指引和启发。