102. 二叉树的层序遍历递归法:深度优先搜索的巧妙应用
二叉树的层序遍历是一种经典的遍历方式,它要求按层级逐层访问二叉树的节点。通常我们会使用队列来实现层序遍历,但递归法也是一种可行且有趣的思路。本文将深入探讨递归法解决二叉树层序遍历的核心难点,并结合代码和模拟过程进行详细讲解。
一、题目描述
给定一个二叉树的根节点 root
,返回其节点值的层序遍历结果,即逐层遍历,从左到右访问所有节点。例如,输入二叉树:
3/ \9 20/ \15 7
输出结果应为:
[[3], [9, 20], [15, 7]]
。
二、核心难点分析
难点1:临时数组的创建逻辑
在递归法中,我们需要预先创建好数组来存储每一层的节点值。这里的关键是如何根据当前节点的深度(层级)来确定将节点值存储到哪个数组中。
- 深度标记:
我们使用一个变量deep
来表示当前节点所在的深度。初始时,根节点的深度为1
。 - 数组创建与填充:
在递归过程中,每当遇到一个新的深度deep
时,我们需要确保res
中已经有足够的子列表来存储该层的节点值。如果res
的大小小于deep
,则创建一个新的空列表并添加到res
中。然后,将当前节点的值添加到res
中对应深度的子列表中。
难点2:递归的具体逻辑实现
递归法的核心在于如何通过递归调用自身来遍历二叉树的每一层。
- 递归终止条件:
当遇到null
节点时,递归结束。这是因为null
节点没有值,也没有子节点,不需要进行任何处理。 - 递归调用:
对于每个非null
节点,我们先将其值添加到对应深度的子列表中,然后分别对其左子节点和右子节点进行递归调用。在递归调用时,深度deep
需要加1
,以表示进入了下一层。
三、解题思路分步解析
步骤1:初始化结果列表和递归调用
public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<>();levelOrderBFS(res, root, 0);return res;
}
这里我们首先创建了一个空的结果列表 res
,然后调用递归函数 levelOrderBFS
开始遍历。初始深度设为 0
,因为在递归函数内部会先将深度加 1
再进行处理。
步骤2:递归函数实现
public void levelOrderBFS(List<List<Integer>> res, TreeNode root, int deep) {if (root == null) {return;}deep++;while (res.size() < deep) {List<Integer> resList = new ArrayList<>();res.add(resList);}res.get(deep - 1).add(root.val);levelOrderBFS(res, root.left, deep);levelOrderBFS(res, root.right, deep);
}
- 深度处理:
首先将深度deep
加1
,因为当前节点的深度比其父节点大1
。 - 数组创建与填充:
使用while
循环检查res
的大小是否小于deep
。如果是,则创建一个新的空列表resList
并添加到res
中。这确保了res
中始终有足够的子列表来存储每一层的节点值。
然后,通过res.get(deep - 1).add(root.val)
将当前节点的值添加到res
中对应深度的子列表中。 - 递归调用:
最后,分别对当前节点的左子节点和右子节点进行递归调用,深度deep
保持不变。这使得递归能够逐层深入,遍历整个二叉树。
四、递归流程模拟
以二叉树 [3, 9, 20, null, null, 15, 7]
为例,结构如下:
3/ \9 20/ \15 7
以下是递归过程的详细模拟:
初始状态
res = []
,deep = 0
,root = 3
第一次递归调用
deep = 1
res.size() = 0 < 1
,创建新列表[]
并添加到res
中,此时res = [[]]
- 将
3
添加到res[0]
中,此时res = [[3]]
- 对
root.left
(即9
)进行递归调用,deep = 1
- 对
root.right
(即20
)进行递归调用,deep = 1
对 9
的递归调用
deep = 2
res.size() = 1 < 2
,创建新列表[]
并添加到res
中,此时res = [[3], []]
- 将
9
添加到res[1]
中,此时res = [[3], [9]]
- 对
root.left
(即null
)进行递归调用,deep = 2
- 对
root.right
(即null
)进行递归调用,deep = 2
对 20
的递归调用
deep = 2
res.size() = 2 == 2
,直接将20
添加到res[1]
中,此时res = [[3], [9, 20]]
- 对
root.left
(即15
)进行递归调用,deep = 2
- 对
root.right
(即7
)进行递归调用,deep = 2
对 15
的递归调用
deep = 3
res.size() = 2 < 3
,创建新列表[]
并添加到res
中,此时res = [[3], [9, 20], []]
- 将
15
添加到res[2]
中,此时res = [[3], [9, 20], [15]]
- 对
root.left
(即null
)进行递归调用,deep = 3
- 对
root.right
(即null
)进行递归调用,deep = 3
对 7
的递归调用
deep = 3
res.size() = 3 == 3
,直接将7
添加到res[2]
中,此时res = [[3], [9, 20], [15, 7]]
- 对
root.left
(即null
)进行递归调用,deep = 3
- 对
root.right
(即null
)进行递归调用,deep = 3
最终结果
res = [[3], [9, 20], [15, 7]]
五、关键知识点总结
1. 递归的基本概念
递归是一种解决问题的方法,它通过将问题分解为更小的子问题,并通过调用自身来解决这些子问题。在二叉树的层序遍历中,我们通过递归调用自身来遍历每一层的节点。
2. 深度优先搜索(DFS)与广度优先搜索(BFS)
递归法本质上是一种深度优先搜索(DFS)的应用。与广度优先搜索(BFS)不同,DFS会沿着一条路径一直深入下去,直到无法继续,然后再回溯到上一层,继续探索其他路径。在层序遍历中,我们通过递归实现了一种特殊的DFS,它能够逐层遍历二叉树的节点。
3. 动态数组的使用
在递归过程中,我们使用了一个动态数组 res
来存储每一层的节点值。动态数组的特点是可以根据需要动态地增加或减少元素,这使得我们能够灵活地处理不同层级的节点数量。
六、完整代码
import java.util.ArrayList;
import java.util.List;class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val = val; }TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}
}class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<>();levelOrderBFS(res, root, 0);return res;}public void levelOrderBFS(List<List<Integer>> res, TreeNode root, int deep) {if (root == null) {return;}deep++;while (res.size() < deep) {List<Integer> resList = new ArrayList<>();res.add(resList);}res.get(deep - 1).add(root.val);levelOrderBFS(res, root.left, deep);levelOrderBFS(res, root.right, deep);}
}
七、总结
递归法解决二叉树的层序遍历问题虽然不像队列法那样直观,但它展示了深度优先搜索在树形结构中的巧妙应用。通过理解临时数组的创建逻辑和递归的具体实现,我们能够更好地掌握递归的思想和技巧。在实际应用中,递归法可能在某些情况下比队列法更简洁高效,尤其是对于一些复杂的树形结构或需要深度优先处理的问题。希望本文的讲解能够帮助读者更好地理解和掌握递归法解决二叉树层序遍历的核心难点。