对于二叉树基本就两种遍历形式,bfs与dfs。
1.dfs相对简单,但复杂的dfs都以其为基础:
//dfs
private static void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
//中序遍历
System.out.print(root.val+" ");
dfs(root.right);
}
对于遍历的顺序取决于你将其他代码写在何处,中间就是中序,其他依次类推。
2. bfs作为层次遍历也有其优势,尽管需要额外的空间来保证顺序,但本身的层次可以解决许多问题。
//bfs
private static List<List<Integer>> bfs(TreeNode root){
//队列存依次节点
Deque<TreeNode> q = new LinkedList<>();
//可以加一个res输出层次顺序
List<List<Integer>> res = new ArrayList<>();
//根节点先入队
q.offerLast(root);
while (!q.isEmpty()){
//记录每一层的节点
List<Integer> list = new ArrayList<>();
//这一步很重要,因为队列在变,要先存下大小
int size = q.size();
//对队列里的节点(本层的节点)记录
for (int i = 0; i < size; i++) {
//取出当前节点
TreeNode node = q.pollFirst();
list.add(node.val);
//将左右节点入队
if(node.left!=null){
q.offerLast(node.left);
}
if(node.right!=null) {
q.offerLast(node.right);
}
}
res.add(list);
}
return res;
}
//构建一个二叉树节点类
class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(){};
TreeNode(int x){
val = x;
}
}
通过二叉树的层次遍历建立二叉树
//按照层次遍历建树
public static TreeNode BuildTree(int[] n){
Queue<TreeNode> q = new LinkedList<>();
//根节点入队
TreeNode root = new TreeNode(n[0]);
q.offer(root);
//实时记录创建到那个节点了
int cur = 1;
while(!q.isEmpty()){
TreeNode node = q.poll();
//看看是不是空节点,本次-1来标注
if(cur<n.length&&n[cur]!=-1){
System.out.println(n[cur]);
TreeNode left = new TreeNode(n[cur]);
q.offer(left);
node.left = left;
}
cur++;
//继续建立右子树
if(cur<n.length&&n[cur]!=-1){
System.out.println(n[cur]);
TreeNode right = new TreeNode(n[cur]);
q.offer(right);
node.right = right;
}
cur++;
}
return root;
}
//dfs思路解决
class Solution {
boolean res = false;
public boolean hasPathSum(TreeNode root, int targetSum) {
dfs(root,targetSum);
return res;
}
private void dfs(TreeNode root,int target){
if(root==null){
return;
}
//将target作为传递参数,到0作为满足的节点
target -= root.val;
//确定根节点时是不是到达0(总和为target)
if(root.left==null&&root.right==null&&target==0){
res = true;
}
//递归
dfs(root.left,target);
dfs(root.right,target);
}
}
路径和2则要输出路径
class Solution {
//存所有满足的路径
List<List<Integer>> res = new ArrayList<>();
//存当前路径
List<Integer> list = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root,targetSum);
return res;
}
private void dfs(TreeNode root,int target){
if(root==null){
return;
}
target -= root.val;
//记入当前节点
list.add(root.val);
if(root.left==null&&root.right==null&&target==0){
//记录满足路径
res.add(new ArrayList(list));
//记得回溯
list.remove(list.size()-1);
return;
}
dfs(root.left,target);
dfs(root.right,target);
//回溯节点状态
list.remove(list.size()-1);
}
}
对于这种不在根节点状态结束的问题,一般思路在于后根遍历,将状态上传,将遍历的节点作为当前的根节点,判断左右子树的状态来满足题意。
class Solution {
//确保出现负值时的正确性
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return res;
}
//返回值为int,为满足状态自底向上传递
private int dfs(TreeNode root){
if(root==null){
return 0;
}
//通过记录左右子树的状态来选择
//我们只选择能对临时这个子树的正贡献值作为这个子树分支的值
int left = Math.max(dfs(root.left),0);
int right = Math.max(dfs(root.right),0);
//实时记录这个子树的最大贡献值
res = Math.max(res,left+right+root.val);
//向上传递时选择贡献最大的子树
return root.val+Math.max(left,right);
}
}
总结: 对于路径和问题基本dfs都可以暴力解决,遍历下去。对于直至根节点的题,比较简单,判断到了根节点在考虑是否满足题意,比如是否到target的和。而对于随意的路径和,则要动态考虑每一个子树,对于这种问题一般选用带返回值的dfs,记录当前节点的状态,用后根遍历的方式传递参数直达root,动态取结果。
本题基本解答方法是使用hashmap来记录父子节点,但本文给出一个更符合思路的后根遍历模板
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return dfs(root,p,q);
}
private TreeNode dfs(TreeNode root,TreeNode p,TreeNode q){
//为空就上传空,有p,q也向上传递
if(root==null||root==q||root==p){
return root;
}
//记录当前左右子树传递值
TreeNode left = dfs(root.left,p,q);
TreeNode right = dfs(root.right,p,q);
//左子树为空,直接返回右子树(右子树也空,传的就是空,为p,q就向上传递)
//左子树不为空,说明有p,q,看看右子树也有的话。返回root则为其公共祖先,右子树没有就返回含有值的左子树
return left == null ? right : right == null ? left : root;
}
}
总结: 二叉树的题最多在于dfs的路径上,用dfs的方法来一点点整理状态就可解决。本身是一种暴力遍历,基本都能遍历到,剪枝就是和回溯联系在一起,要考虑好临时的遍历的回退。而层次遍历的问题基本围绕基本模板开展,本身遍历顺序的局限性,解决类似路径问题稍微复杂一点。