树形动态规划(Tree DP)算法详解
什么是树形动态规划
树形动态规划(Tree DP)是一种在树形结构上进行推导的动态规划方法。与常规动态规划不同,树形DP的求解过程通常以节点从深到浅(子树从小到大)的顺序作为动态规划的"阶段"。
树形DP问题的特点:
- 数据结构为树形结构(二叉树或多叉树)
- 状态转移发生在父子节点之间
- 通常采用深度优先搜索(DFS)进行遍历
树形DP的分类
按转移方向分类
- 自底向上:通过递归先求解子树,然后在回溯时自底向上进行状态转移
- 自顶向下:从根节点开始向下递归,逐层计算子节点状态(较少使用)
按根节点是否固定分类
- 固定根的树形DP:指定了根节点,通常只需一次DFS
- 不定根的树形DP:根节点不固定,通常需要两次DFS(预处理+换根)
固定根的树形DP实例
二叉树中的最大路径和
问题描述:给定一个二叉树,找到路径和最大的路径,路径可以不经过根节点。
解题思路:
- 定义递归函数计算每个节点的最大贡献值
- 维护全局最大路径和变量
- 节点最大贡献值 = 当前节点值 + max(左子树贡献, 右子树贡献)
- 全局最大路径和 = max(全局最大, 左贡献+右贡献+当前值)
class Solution:
def __init__(self):
self.ans = float('-inf')
def dfs(self, node):
if not node:
return 0
left_max = max(self.dfs(node.left), 0)
right_max = max(self.dfs(node.right), 0)
cur_max = left_max + right_max + node.val
self.ans = max(self.ans, cur_max)
return max(left_max, right_max) + node.val
def maxPathSum(self, root):
self.dfs(root)
return self.ans
相邻字符不同的最长路径
问题描述:在树中找到最长路径,要求路径上相邻节点字符不同。
解题思路:
- 构建树结构(邻接表)
- 定义DFS函数计算每个节点的最长路径
- 维护全局最长路径变量
- 只有当相邻节点字符不同时才更新路径长度
class Solution:
def longestPath(self, parent, s):
size = len(parent)
graph = [[] for _ in range(size)]
for i in range(1, size):
graph[parent[i]].append(i)
ans = 0
def dfs(u):
nonlocal ans
u_len = 0
for v in graph[u]:
v_len = dfs(v)
if s[u] != s[v]:
ans = max(ans, u_len + v_len + 1)
u_len = max(u_len, v_len + 1)
return u_len
dfs(0)
return ans + 1
不定根的树形DP实例
最小高度树
问题描述:找到树中所有能使树高度最小的根节点。
解题思路:
- 第一次DFS:自底向上计算每个节点向下走的最长和次长路径
- 第二次DFS:自顶向下计算每个节点向上走的最长路径
- 综合向上和向下路径得到每个节点作为根时的树高
- 找出所有最小高度的根节点
class Solution:
def findMinHeightTrees(self, n, edges):
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
down1 = [0] * n # 最长向下路径
down2 = [0] * n # 次长向下路径
p = [0] * n # 记录最长路径的子节点
def dfs(u, fa):
for v in graph[u]:
if v == fa:
continue
dfs(v, u)
height = down1[v] + 1
if height >= down1[u]:
down2[u] = down1[u]
down1[u] = height
p[u] = v
elif height > down2[u]:
down2[u] = height
up = [0] * n # 最长向上路径
def reroot(u, fa):
for v in graph[u]:
if v == fa:
continue
if p[u] == v:
up[v] = max(up[u], down2[u]) + 1
else:
up[v] = max(up[u], down1[u]) + 1
reroot(v, u)
dfs(0, -1)
reroot(0, -1)
min_h = min(max(down1[i], up[i]) for i in range(n))
return [i for i in range(n) if max(down1[i], up[i]) == min_h]
树形DP的复杂度分析
对于大多数树形DP问题:
- 时间复杂度:O(n),n为节点数,每个节点只被访问常数次
- 空间复杂度:O(n),用于存储树结构和DP状态
总结
树形动态规划是处理树结构问题的有力工具,掌握它需要:
- 理解树的基本遍历方式(DFS为主)
- 学会设计合理的状态表示
- 掌握状态转移方程的推导
- 熟悉固定根和不定根两种情况的处理方法
通过本文的几个经典例题,读者可以逐步掌握树形DP的核心思想和解题技巧,为解决更复杂的树形问题打下坚实基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考