二叉树与搜索图论

本文件维护第一章第二类题目的题解与面试追问。字节原文中的二叉树、网格搜索和图论题保持在同一分类。

字节核心题单

顺序 题目 字节频次 数据版本 其他统计覆盖
1 LC 200 岛屿数量 50 08 更新 3/4
2 LC 236 二叉树的最近公共祖先 41 08 更新 2/4
3 LC 103 二叉树的锯齿形层序遍历 28 08 更新 2/4
4 LC 102 二叉树的层序遍历 27 08 更新 4/4
5 LC 199 二叉树的右视图 26 08 更新 2/4
6 LC 572 另一棵树的子树 17 08 更新 0/4
7 LC 104 二叉树的最大深度 22 04 基线 1/4
8 LC 94 二叉树的中序遍历 18 04 基线 1/4
9 LC 124 二叉树中的最大路径和 15 04 基线 3/4
10 LC 226 翻转二叉树 10 04 基线 0/4
11 LC 695 岛屿的最大面积 未单列 08 关联题 1/4

其他来源新增题

题目 发现来源
LC 108 将有序数组转换为二叉搜索树 Hot 100 综合榜
LC 111 二叉树的最小深度 美团
LC 114 二叉树展开为链表 Hot 100 综合榜
LC 144 二叉树的前序遍历 美团
LC 207 课程表 华为、Hot 100 综合榜
LC 208 实现 Trie(前缀树) Hot 100 综合榜
LC 230 二叉搜索树中第 K 小的元素 Hot 100 综合榜
LC 437 路径总和 III Hot 100 综合榜

题解与追问重点

  • 每道递归题先定义函数语义、返回值和终止条件,再写代码。
  • 遍历题比较递归与迭代;层序题统一使用队列模板,并讲清每层边界。
  • 岛屿题比较 DFS、BFS 和并查集,说明原地标记与额外访问集合的取舍。
  • 图论题补充有向图判环、拓扑排序和访问状态设计。
  • 树题追问优先覆盖递归深度、退化树栈溢出、返回路径或具体节点等变体。

LC 200 岛屿数量

考频与考点:字节 50 次,本类最高频。考查网格建图、连通分量搜索和访问标记。

写代码前确认:网格是否允许原地修改;相邻是否只含上下左右。本题可修改时,将访问过的 "1" 改成 "0",无需额外集合。

思路/解法:扫描每个格子。遇到尚未访问的陆地,答案加一,再从它出发用 DFS 淹没整个岛屿。递归 DFS 可写得更短,但极端网格可能超过 Python 递归深度,下面使用显式栈。BFS 只需将栈换成队列,复杂度相同。若需要动态合并陆地或频繁查询连通性,可用并查集;对一次静态扫描没有优势。

from typing import List


class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid or not grid[0]:
            return 0

        rows, cols = len(grid), len(grid[0])
        answer = 0
        for r in range(rows):
            for c in range(cols):
                if grid[r][c] != "1":
                    continue
                answer += 1
                grid[r][c] = "0"
                stack = [(r, c)]
                while stack:
                    x, y = stack.pop()
                    for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                        nx, ny = x + dx, y + dy
                        if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == "1":
                            grid[nx][ny] = "0"  # 入栈时标记,避免重复入栈
                            stack.append((nx, ny))
        return answer

复杂度:时间 O(mn),每个格子至多处理一次;显式栈最坏 O(mn)。

边界/易错点:空网格、全水、单格岛屿;必须在入栈或入队时标记,不能等弹出后再标记。

面试追问:不能修改输入时如何用 visited?八方向相邻如何改?陆地逐个加入并实时询问岛屿数时如何用并查集?

LC 236 二叉树的最近公共祖先

考频与考点:字节 41 次。考查后序递归、子树信息向上传递,以及节点引用与节点值的区别。

写代码前确认:p、q 是否都存在且节点值是否唯一。原题保证都存在,应按节点对象比较。

思路/解法:定义 dfs(node):若当前子树包含 p 或 q,返回能够代表该目标或其最近公共祖先的节点;都不含则返回空。左右子树均返回非空时,当前节点就是答案;只有一侧非空则向上传递该侧结果。

class Solution:
    def lowestCommonAncestor(self, root: "TreeNode", p: "TreeNode", q: "TreeNode") -> "TreeNode":
        def dfs(node: "TreeNode | None") -> "TreeNode | None":
            if node is None or node is p or node is q:
                return node
            left = dfs(node.left)
            right = dfs(node.right)
            if left is not None and right is not None:
                return node
            return left if left is not None else right

        return dfs(root)

复杂度:时间 O(n);递归栈 O(h),退化树最坏 O(n)。

边界/易错点:一个目标是另一个目标的祖先时,命中目标应立即返回;不要只比较 val。

面试追问:若节点可能不存在,如何同时返回命中数量?BST 如何利用大小关系?多次 LCA 查询如何做倍增预处理?

LC 103 二叉树的锯齿形层序遍历

考频与考点:字节 28 次。考查层序边界、双端队列以及方向切换。

写代码前确认:空树返回空列表;第一层方向为从左到右。

思路/解法:BFS 每轮先固定 len(queue),确保只消费当前层。树节点始终按左右顺序入队;输出时根据方向追加到本层结果的右端或左端,避免每层结束后再反转。DFS 也可按深度建立结果,并依据奇偶深度插入两端,但会承受递归栈风险。

from collections import deque
from typing import List, Optional


class Solution:
    def zigzagLevelOrder(self, root: Optional["TreeNode"]) -> List[List[int]]:
        if root is None:
            return []
        queue = deque([root])
        left_to_right = True
        answer = []
        while queue:
            level = deque()
            for _ in range(len(queue)):
                node = queue.popleft()
                if left_to_right:
                    level.append(node.val)
                else:
                    level.appendleft(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            answer.append(list(level))
            left_to_right = not left_to_right
        return answer

复杂度:时间 O(n),队列与结果之外的辅助空间最坏 O(w),w 为最大层宽。

边界/易错点:不要改变子节点入队顺序来制造锯齿,否则下一层的结构顺序容易混乱。

面试追问:如何只用一个双端队列完成双向出队?若允许每层反转,代码和复杂度如何变化?

LC 102 二叉树的层序遍历

考频与考点:字节 27 次,多家公司共同高频。考查 BFS 队列和分层控制。

写代码前确认:返回值按层分组,而不是一维访问序列。

思路/解法:队列保存下一批待访问节点。每轮记录当前长度,只弹出这些节点,因此新加入的子节点自然属于下一层。递归方案可定义 dfs(node, depth),首次到达新深度时创建列表,再追加节点值;BFS 更贴合题意。

from collections import deque
from typing import List, Optional


class Solution:
    def levelOrder(self, root: Optional["TreeNode"]) -> List[List[int]]:
        if root is None:
            return []
        answer, queue = [], deque([root])
        while queue:
            level = []
            for _ in range(len(queue)):
                node = queue.popleft()
                level.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            answer.append(level)
        return answer

复杂度:时间 O(n);队列辅助空间 O(w),输出空间 O(n)。

边界/易错点:循环中必须保存本层长度;直接遍历不断增长的队列会丢失层边界。

面试追问:如何返回自底向上的层序?如何计算每层平均值?递归层序在退化树上有什么风险?

LC 199 二叉树的右视图

考频与考点:字节 26 次。考查层序遍历变体与先右后左的深度优先搜索。

写代码前确认:右视图是每一深度最右侧可见节点,并不等于沿 right 指针形成的链。

思路/解法:BFS 中每层最后弹出的节点就是该层最右节点。另一种常考写法是先右后左 DFS:定义 dfs(node, depth) 访问子树,当 depth == len(answer) 时说明这是该深度首次到达的节点,直接记录。

from collections import deque
from typing import List, Optional


class Solution:
    def rightSideView(self, root: Optional["TreeNode"]) -> List[int]:
        if root is None:
            return []
        answer, queue = [], deque([root])
        while queue:
            level_size = len(queue)
            for index in range(level_size):
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
                if index == level_size - 1:
                    answer.append(node.val)
        return answer

复杂度:时间 O(n),辅助空间 O(w);DFS 方案为 O(h) 递归栈。

边界/易错点:某层最右节点可能来自左子树;实现时可直接在 index == level_size - 1 时记录。

面试追问:如何改为左视图?如何用 DFS 保证每层首个节点就是答案?如何返回每层最右节点对象?

LC 572 另一棵树的子树

考频与考点:字节 17 次。考查“枚举候选根 + 判断两棵树相同”的递归拆分。

写代码前确认:原题 subRoot 非空;相同必须同时满足结构和值一致,不能只比较某种遍历值序列。

思路/解法:定义 same(a, b):判断两棵以 a、b 为根的树是否完全相同。再定义 contains(node):判断 node 子树中是否存在与 subRoot 相同的根,检查当前节点后递归左右子树。

from typing import Optional


class Solution:
    def isSubtree(self, root: Optional["TreeNode"], subRoot: Optional["TreeNode"]) -> bool:
        def same(a: Optional["TreeNode"], b: Optional["TreeNode"]) -> bool:
            if a is None or b is None:
                return a is b
            return a.val == b.val and same(a.left, b.left) and same(a.right, b.right)

        def contains(node: Optional["TreeNode"]) -> bool:
            if node is None:
                return False
            return same(node, subRoot) or contains(node.left) or contains(node.right)

        return contains(root)

复杂度:最坏时间 O(nm),递归栈 O(h_root + h_sub);n、m 为两树节点数。

边界/易错点:序列化方案必须记录空节点和分隔符,否则不同结构或多位数可能误匹配。

面试追问:如何用带空标记的序列化加 KMP 降低匹配复杂度?如何用子树哈希比较?

LC 104 二叉树的最大深度

考频与考点:字节 22 次。考查最基础的树递归语义和高度定义。

写代码前确认:空树深度为 0,单节点树深度为 1。

思路/解法:定义 depth(node):返回以 node 为根的子树最大深度。空节点为 0,非空节点为左右子树较大深度加一。迭代层序每完成一层将深度加一,可规避退化树的递归深度限制。

from typing import Optional


class Solution:
    def maxDepth(self, root: Optional["TreeNode"]) -> int:
        def depth(node: Optional["TreeNode"]) -> int:
            if node is None:
                return 0
            return max(depth(node.left), depth(node.right)) + 1

        return depth(root)

复杂度:时间 O(n);递归栈 O(h)。

边界/易错点:不要混淆节点数定义的深度与边数定义的高度;本题按节点数返回。

面试追问:如何改为最小深度?如何在求深度的同时判断平衡树?极深树如何迭代实现?

LC 94 二叉树的中序遍历

考频与考点:字节 18 次。考查递归展开与显式栈模拟。

写代码前确认:顺序为左子树、根、右子树;是否要求避免递归。

思路/解法:递归中定义 dfs(node):按中序把当前子树节点值追加到结果。迭代法用栈保存“左链上尚未访问的根”,先一路压左,弹栈访问,再转向右子树。面试通常应掌握两种。

from typing import List, Optional


class Solution:
    def inorderTraversal(self, root: Optional["TreeNode"]) -> List[int]:
        answer, stack = [], []
        current = root
        while current is not None or stack:
            while current is not None:
                stack.append(current)
                current = current.left
            current = stack.pop()
            answer.append(current.val)
            current = current.right
        return answer

递归核心等价于:dfs(node.left)、记录 node.val、dfs(node.right);迭代法把函数调用栈显式化,复杂度不变。

复杂度:时间 O(n);栈空间 O(h)。

边界/易错点:外层条件必须是 current is not None or stack;弹栈访问后转向右子树。

面试追问:如何写前序、后序迭代?Morris 中序如何做到 O(1) 额外空间,它是否临时修改树?

LC 124 二叉树中的最大路径和

考频与考点:字节 15 次,多家公司高频。考查树形 DP、全局答案与返回值语义分离。

写代码前确认:路径至少包含一个节点;可从任意节点开始和结束,不能重复节点;节点值可能全为负数。

思路/解法:定义 gain(node):从 node 出发、只能向下选择一条分支时能提供给父节点的最大贡献。负贡献应舍弃为 0。经过当前节点且同时连接左右分支的路径不能继续向父节点延伸,只用于更新全局答案。

from typing import Optional


class Solution:
    def maxPathSum(self, root: Optional["TreeNode"]) -> int:
        answer = float("-inf")

        def gain(node: Optional["TreeNode"]) -> int:
            nonlocal answer
            if node is None:
                return 0
            left = max(gain(node.left), 0)
            right = max(gain(node.right), 0)
            answer = max(answer, node.val + left + right)
            return node.val + max(left, right)

        gain(root)
        return int(answer)

复杂度:时间 O(n);递归栈 O(h)。

边界/易错点:全负树不能把答案初始化为 0;返回父节点时不能同时带上左右两条分支。

面试追问:如何返回具体路径?如何改为只允许叶子到叶子?与二叉树直径的递推关系有何异同?

LC 226 翻转二叉树

考频与考点:字节 10 次。考查递归定义、原地修改与树遍历。

写代码前确认:是否允许修改原树。原题要求返回翻转后的根,通常原地交换左右子树。

思路/解法:定义 invert(node):翻转以 node 为根的整棵子树并返回其根。先递归翻转左右子树,再交换返回结果。也可 BFS 逐节点交换,避免递归栈过深。

from typing import Optional


class Solution:
    def invertTree(self, root: Optional["TreeNode"]) -> Optional["TreeNode"]:
        def invert(node: Optional["TreeNode"]) -> Optional["TreeNode"]:
            if node is None:
                return None
            left = invert(node.left)
            right = invert(node.right)
            node.left, node.right = right, left
            return node

        return invert(root)

复杂度:时间 O(n);递归栈 O(h)。

边界/易错点:若先覆盖一个子指针再赋另一个,会丢失原子树;应使用并行赋值或临时变量。

面试追问:如何生成一棵新镜像树而不修改原树?如何迭代实现?翻转两次后树是否必然恢复?

LC 695 岛屿的最大面积

考频与考点:字节关联题。考查连通分量搜索过程中聚合节点数量。

写代码前确认:面积按四方向相邻的陆地格数计算;是否允许原地修改输入。

思路/解法:与 LC 200 相同地枚举每个未访问陆地,但每次搜索累计当前连通分量面积,并更新最大值。下面采用迭代 DFS。BFS 的访问顺序不同,结果和复杂度相同;并查集适合需要合并、查询多个分量大小的场景。

from typing import List


class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        if not grid or not grid[0]:
            return 0
        rows, cols = len(grid), len(grid[0])
        answer = 0
        for r in range(rows):
            for c in range(cols):
                if grid[r][c] != 1:
                    continue
                area = 0
                grid[r][c] = 0
                stack = [(r, c)]
                while stack:
                    x, y = stack.pop()
                    area += 1
                    for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                        nx, ny = x + dx, y + dy
                        if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == 1:
                            grid[nx][ny] = 0
                            stack.append((nx, ny))
                answer = max(answer, area)
        return answer

复杂度:时间 O(mn);栈最坏 O(mn)。

边界/易错点:全水时返回 0;应按连通分量重置 area,并在入栈时标记。

面试追问:如何返回最大岛屿的坐标集合?若翻转一个水格,如何求最大可能面积?

LC 108 将有序数组转换为二叉搜索树

考频与考点:其他来源新增。考查分治构造、BST 中序有序性质和平衡条件。

写代码前确认:输入严格递增;高度平衡只要求任意节点左右子树高度差不超过一,答案不唯一。

思路/解法:定义 build(left, right):用 nums[left:right + 1] 构造一棵平衡 BST 并返回根。选中点为根后,左右区间长度至多差一,递归构造左右子树。使用下标而非切片,避免额外复制。

from typing import List, Optional


class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional["TreeNode"]:
        def build(left: int, right: int) -> Optional["TreeNode"]:
            if left > right:
                return None
            middle = left + (right - left) // 2
            root = TreeNode(nums[middle])
            root.left = build(left, middle - 1)
            root.right = build(middle + 1, right)
            return root

        return build(0, len(nums) - 1)

复杂度:时间 O(n);递归栈 O(log n),构造结果占 O(n)。

边界/易错点:空区间终止条件是 left > right;左右中点任选其一都合法。

面试追问:输入为有序链表时如何做到 O(n)?如何证明中点构造必然高度平衡?

LC 111 二叉树的最小深度

考频与考点:其他来源新增。考查叶节点定义和 BFS 最短性。

写代码前确认:最小深度是根到最近叶节点的节点数;只有一个孩子的节点不是叶子。

思路/解法:BFS 按层搜索,首次遇到左右孩子都为空的节点即可返回当前深度,因为队列保证后续节点不会更浅。递归也可定义 depth(node) 返回子树最小深度,但单侧为空时不能直接取 min(0, x)。

from collections import deque
from typing import Optional


class Solution:
    def minDepth(self, root: Optional["TreeNode"]) -> int:
        if root is None:
            return 0
        queue = deque([(root, 1)])
        while queue:
            node, depth = queue.popleft()
            if node.left is None and node.right is None:
                return depth
            if node.left:
                queue.append((node.left, depth + 1))
            if node.right:
                queue.append((node.right, depth + 1))
        return 0

复杂度:最坏时间 O(n);队列空间 O(w)。若浅层很快出现叶子,BFS 可提前结束。

边界/易错点:根只有右子树时,答案必须沿右侧到叶子,不能把缺失的左子树深度当作 0 参与最小值。

面试追问:递归应如何处理单侧为空?若边带非负权,如何改用 Dijkstra 求最短根叶路径?

LC 114 二叉树展开为链表

考频与考点:其他来源新增。考查原地指针重排、前序顺序和后序递归信息。

写代码前确认:必须原地展开;所有 left 置空,right 链顺序等于原树前序遍历。

思路/解法:定义 flatten_tree(node):原地展开当前子树,并返回展开链表的尾节点。先保存并展开左右子树;若左子树存在,将其接到根的右侧,再把原右链接到左链尾部。显式栈按前序取节点并连接前驱也可实现,空间同为 O(h) 到 O(n)。

from typing import Optional


class Solution:
    def flatten(self, root: Optional["TreeNode"]) -> None:
        def flatten_tree(node: Optional["TreeNode"]) -> Optional["TreeNode"]:
            if node is None:
                return None
            left_root, right_root = node.left, node.right
            left_tail = flatten_tree(left_root)
            right_tail = flatten_tree(right_root)
            if left_root is not None:
                node.right = left_root
                node.left = None
                left_tail.right = right_root
            return right_tail or left_tail or node

        flatten_tree(root)

复杂度:时间 O(n);递归栈 O(h),不计递归栈的额外空间为 O(1)。

边界/易错点:递归前先保存原右子树;不要每次扫描左链尾部,否则退化为 O(n^2)。

面试追问:如何用前驱节点实现 O(1) 额外空间?如何展开为中序顺序?

LC 144 二叉树的前序遍历

考频与考点:其他来源新增。考查根、左、右的访问顺序及迭代模拟。

写代码前确认:空树返回空列表;是否限制使用递归。

思路/解法:递归函数 dfs(node) 的语义是按前序把当前子树追加到结果。迭代法弹出根后先压右孩子、再压左孩子,利用栈后进先出保证左子树先访问。

from typing import List, Optional


class Solution:
    def preorderTraversal(self, root: Optional["TreeNode"]) -> List[int]:
        if root is None:
            return []
        answer, stack = [], [root]
        while stack:
            node = stack.pop()
            answer.append(node.val)
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return answer

复杂度:时间 O(n);显式栈最坏 O(h)。

边界/易错点:压栈顺序与访问顺序相反;若先压左孩子,实际会先访问右子树。

面试追问:如何统一写出前、中、后序迭代模板?Morris 前序如何恢复临时线索?

LC 207 课程表

考频与考点:华为与 Hot 100 新增。考查有向图判环、拓扑排序和入度维护。

写代码前确认:[course, prerequisite] 表示从先修课指向课程;只需判断能否完成,不要求返回具体顺序。

思路/解法:Kahn BFS 先把所有入度为零的课程入队。每学完一门课,就删除它的出边并降低后继入度;最终处理数量等于课程总数则无环。三色 DFS 也可判环:0 未访问、1 当前递归路径、2 已完成,遇到状态 1 即发现环;BFS 无递归深度风险。

from collections import deque
from typing import List


class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        graph = [[] for _ in range(numCourses)]
        indegree = [0] * numCourses
        for course, prerequisite in prerequisites:
            graph[prerequisite].append(course)
            indegree[course] += 1

        queue = deque(i for i, degree in enumerate(indegree) if degree == 0)
        completed = 0
        while queue:
            course = queue.popleft()
            completed += 1
            for next_course in graph[course]:
                indegree[next_course] -= 1
                if indegree[next_course] == 0:
                    queue.append(next_course)
        return completed == numCourses

复杂度:时间 O(V + E);邻接表、入度和队列空间 O(V + E)。

边界/易错点:建图方向必须与入度定义一致;无先修关系时所有课程都可完成;重复边是否存在应在面试中确认。

面试追问:如何返回一条拓扑序?如何用 DFS 返回具体环?若持续新增依赖,如何维护可行性?

LC 208 实现 Trie(前缀树)

考频与考点:Hot 100 新增。考查多叉树设计、前缀共享与单词结束标记。

写代码前确认:字符集范围;本题为小写英文字母。search 要求完整单词,startsWith 只要求前缀路径存在。

思路/解法:每个节点保存“字符到子节点”的映射和 is_end 标记。插入沿字符创建路径;查询沿路径行走,完整单词还需检查末节点标记。用字典便于适配稀疏字符集;固定 26 长度数组常数更稳定但占用更多空间。

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False


class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True

    def _find(self, text: str):
        node = self.root
        for char in text:
            if char not in node.children:
                return None
            node = node.children[char]
        return node

    def search(self, word: str) -> bool:
        node = self._find(word)
        return node is not None and node.is_end

    def startsWith(self, prefix: str) -> bool:
        return self._find(prefix) is not None

复杂度:单次插入或查询时间 O(L);总空间与所有已创建字符节点数成正比。

边界/易错点:不能用“没有孩子”代表单词结束,因为一个单词可能是另一个单词的前缀。

面试追问:如何支持删除并回收节点?如何返回指定前缀的前 k 个词?字符集很大时节点怎样存储?

LC 230 二叉搜索树中第 K 小的元素

考频与考点:Hot 100 新增。考查 BST 中序有序性质和迭代遍历提前终止。

写代码前确认:k 从 1 开始且合法;BST 是否允许重复值。原题通常按节点排名,不需要去重。

思路/解法:中序遍历 BST 会得到递增序列。使用显式栈压入左链,每弹出一个节点就将 k 减一,减到零立即返回,无需遍历剩余节点。递归也可通过外部计数提前记录答案,但 Python 中迭代更直接。

from typing import Optional


class Solution:
    def kthSmallest(self, root: Optional["TreeNode"], k: int) -> int:
        stack = []
        current = root
        while current is not None or stack:
            while current is not None:
                stack.append(current)
                current = current.left
            current = stack.pop()
            k -= 1
            if k == 0:
                return current.val
            current = current.right
        raise ValueError("k exceeds the number of nodes")

复杂度:时间平均 O(h + k)、最坏 O(n);栈空间 O(h)。

边界/易错点:访问根后必须进入右子树;题目保证合法时不会走到异常分支。

面试追问:若频繁查询第 k 小,如何在节点维护子树大小并做到 O(h)?如何求第 k 大?

LC 437 路径总和 III

考频与考点:Hot 100 新增。考查树上前缀和、哈希计数和回溯恢复现场。

写代码前确认:路径必须向下但不必从根开始或到叶结束;节点值可为负,因此不能使用滑动窗口。

思路/解法:定义 dfs(node, prefix):统计遍历当前子树时,以根到父节点的路径和为 prefix 的所有目标路径。到当前节点的新前缀和为 current,此前出现过 current - targetSum 的次数,就是以当前节点结尾的新路径数。哈希表保存当前递归路径上的前缀和频次;退出节点时减一,防止把兄弟分支拼成路径。

from typing import Optional


class Solution:
    def pathSum(self, root: Optional["TreeNode"], targetSum: int) -> int:
        count = {0: 1}

        def dfs(node: Optional["TreeNode"], prefix: int) -> int:
            if node is None:
                return 0
            current = prefix + node.val
            answer = count.get(current - targetSum, 0)
            count[current] = count.get(current, 0) + 1
            answer += dfs(node.left, current)
            answer += dfs(node.right, current)
            count[current] -= 1
            if count[current] == 0:
                del count[current]
            return answer

        return dfs(root, 0)

复杂度:时间 O(n);哈希表与递归栈空间 O(h)。

边界/易错点:必须初始化 count[0] = 1 才能统计从根开始的路径;回溯时必须撤销当前前缀和。

面试追问:朴素的“从每个节点重新向下搜索”为何是 O(n^2)?如何返回所有具体路径?为什么负数使双指针失效?