刷题总结——树

hayden's blog / 2024-11-17 / 原文

总结刷题中遇到的与树有关问题

遍历问题

前中后序遍历

  • 有递归与迭代两种写法
  • 迭代时需要栈模拟,中序遍历单独注意写法(类似于模拟调用栈),后序遍历可以通过前序遍历+反转的方式实现
题目 LC编号 注意事项
前序 递归 144 正常递归
前序 迭代 144 插入单个root后进行Stack模拟 pop之后插入右左
中序 递归 94 正常递归
中序 迭代 04.06 插入所有left后进行Stack模拟 假设pop的为根节点,插入右和右的所有leftmost节点
后序 递归 145 正常递归
后序 迭代 145 使用前序并且插入顺序变成左右,之后翻转
层序 迭代 102 使用queue容器模拟

步骤

对于遍历类题目,可以按照以下步骤:

  • 要了解所求的答案从哪里来,并将其映射到前中后序遍历和层序遍历中的一种
  • 之后就明确了使用DFS/BFS写法,需要引入哪些额外参数和全局变量:
    • 前序:普通DFS
    • 中序:带有额外input参数(前驱/后继节点)的DFS
    • 后序:带有返回值的DFS,返回值用于根节点相关计算
    • 层序:使用queue模拟实现的BFS

思考

此处引用灵神给的思考题,以及个人的思考

  • 一般来说,DFS 的递归边界是空节点。在什么情况下,要额外把叶子节点作为递归边界?
    • 如果把叶子节点作为递归边界,在写DFS的时候就要check加入的是否为空:
      if (node->left) {
      dfs(node->left, seq);
      }
      if (node->right) {
      dfs(node->right, seq);
      }
    • 有的时候可能要统计的是具体的某种性质的节点(比如左叶子/右叶子),这种情况下可能dfs函数需要使用叶子节点判断,然后进行相应计算
  • 在什么情况下,DFS 需要有返回值?什么情况下不需要有返回值?
    • DFS返回值的作用是用于帮助根节点计算当前节点的结果,即当前目标值计算需要依靠左右子树dfs(root->left), dfs(root->right)各自的DFS返回值,如果不需要则可以不设置返回值
    • 从DFS返回值到当前节点结果计算可以认为是自底向上“归”的过程,相当于后序遍历postorder
  • 在什么情况下,题目更适合用自顶向下的方法解决?什么情况下更适合用自底向上的方法解决?
    • 自顶向下pre-order:划分子问题并传入参数进入下层DFS,之后逐一遍历并且利用全局变量作最大值/最小值统计,dfs函数本身不需要返回值
    • 自底向上post-order:汇总子节点DFS返回值,结合当前节点的值进行统计得到当前节点的答案,再利用全局变量作最大值/最小值统计,通常无需传入参数
    • 一些复杂的问题中,DFS返回的和最终统计结果不一致,最终统计结果还需要通过全局变量来统计
  • 在什么情况下,需要设计额外的变量作为DFS递归的额外参数?
    • 有的问题需要在dfs中遍历到每个节点设置状态,并且前一个遍历的节点(前驱)的中间结果需要pass到后继节点使用,这个时候需要设计一些传参用来传递这种信息,通常是中序遍历,比如二叉树转链表/双向链表
    • 自顶向下DFS需要维护统计量(LC题目298)
    • 自底向上DFS中,其实是返回值代替了需要传下去的统计量,少一个入参多一个出参
    • 这种题目可能还伴随设置dummy头节点的处理,用来简化问题

树形DP问题

树的直径:

  • 注意全局变量ans和dfs返回值不一致的情况,ans计算直径/路径,dfs返回的是深度
  • ans可以用全局变量存储,也可以用引用的形式作为dfs的传入参数

树形dp:

  • 树dp并非具有重叠子问题,而是需要在子节点解决之后利用其状态进行当前节点的决策,这一步骤类似于dp中的状态转移
  • 树dp问题有独立集问题和控制集问题,其实都可以归为子节点到父节点之间的状态转移(选/不选,染色),此时要求dfs返回的结果不是一个变量而是一组状态值,可以利用C++17的结构化绑定简化代码写法

LCA问题

二叉树LCA

LC题目235 236
二叉树LCA实际上也是一种后序遍历的递归,但是要注意的是递归函数返回值的定义,此处的定义设计为{p, q, LCA(p, q), none}中的一个;这里的设计是为了p和q一个在左一个在右的情况:

  • 如果左右子树都返回值的时候,说明一个是p一个是q,那么LCA一定是root
  • 如果只有一个子树有返回值,那么返回的一定是LCA节点,直接返回即可

BST是二叉树的特殊情况,如果是BST的话,可直接用值的范围作递归判断,简化了上述条件

一般树LCA

需要使用树上倍增的方法

路径问题

可以分成以下几类:

  • 从根到叶节点的路径,其实就是DFS到叶节点结束,check条件是否满足(LC112 113)
  • 不必从根节点开始,树中的几个节点有边相连即可:后序遍历,DFS函数记录当前子树中从根出发到某个结尾的值,求出左右子树返回值之后,再判断是否可以连接左右子树到根节点,通过全局变量更新结果(LC124 543)
  • 方向从上到下的任意一个路径:前缀和问题,考虑结合hash的路径计算(LC437 面试题 04.12. 求和路径)

参考资料

  1. https://leetcode.cn/circle/discuss/K0n2gO/
  2. https://www.bilibili.com/video/BV1W44y1Z7AR/?vd_source=c80c62eac86a4ba033ab112349bbdd9f
  3. https://lfool.github.io/LFool-Notes/algorithm/二叉树路径相关技巧.html
  4. https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/solutions/2305895/mo-ban-jiang-jie-shu-shang-bei-zeng-suan-v3rw