树——构造和遍历问题——面试专题

重点研究二叉树相关的算法题目,主要是层次遍历和深度优先遍历方面的问题。

通过序列构造二叉树

前中序复原二叉树

(1) 前序:1 2 3 4 5 6 8 7 9 10 11 12 13 15 14

(2) 中序:3 4 8 6 7 5 2 1 10 9 11 15 13 14 12

过程绘制图

中序和后序复原二叉树

(1) 前序:1 2 3 4 5 6 8 7 9 10 11 12 13 15 14

(2) 后序:8 7 6 5 4 3 2 10 15 14 13 12 11 9 1

树的层次遍历

二叉树层次遍历

广度优先又叫层次遍历,基本过程如下:

遍历并输出全部元素

复制代码
    3
   / \
  9  20
    /  \
   15   7

输出
[3, 9, 20, 15, 7]
java 复制代码
List<Integer> simpleLevelOrder(TreeNode root) {
    if (root == null) {
        return new ArrayList<Integer>();
    }
    List<Integer> res = new ArrayList<Integer>();
    LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
    //将根节点放入队列中,然后不断遍历队列
    queue.add(root);
    //有多少元素执行多少次
    while (queue.size() > 0) {
        //获取当前队列的长度,这个长度相当于 当前这一层的节点个数
        TreeNode t = queue.remove();
        res.add(t.val);
        if (t.left != null) {
            queue.add(t.left);
        }
        if (t.right != null) {
            queue.add(t.right);
        }
    }
    return res;
}

按行遍历并输出元素

java 复制代码
二叉树:[3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其层序遍历结果:
 [
  [3],
  [9,20],
  [15,7]
]

如何判断某一层访问完了呢?

简单,用一个变量size标记一下就行了,size表示某一层的元素个数,只要出队,就将size减1,减到0就说明该层元素访问完了。当size变成0之后,这时队列中剩余元素的个数恰好就是下一层元素的个数,因此重新将size标记为下一层的元素个数就可以继续处理新的一行了,例如在上面的序列中:

1.首先拿根节点3,其左/右子结点都不为空,就将其左右放入队列中,因此此时3已经出队了,剩余元素9和20恰好就是第二层的所有结点,此时size=2。

2.继续,将9从队列中拿走,size--变成1,并将其子孩子8和13入队。之后再将20 出队,并将其子孩子15和7入队,此时再次size--,变成9了。当size=0,说明当前层已经处理完了,此时队列有四个元素,而且恰好就是下一层的元素个数。

java 复制代码
public List<List<Integer>> level102Order(TreeNode root) {
    if(root==null) {
        return new ArrayList<List<Integer>>();
    }
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
    //将根节点放入队列中,然后不断遍历队列
    queue.add(root);
    while(queue.size()>0) {
        //获取当前队列的长度,也就是当前这一层的元素个数
        int size = queue.size();
        ArrayList<Integer> tmp = new ArrayList<Integer>();
        //将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
        //如果节点的左/右子树不为空,也放入队列中
        for(int i=0;i<size;++i) {
            TreeNode t = queue.remove();
            tmp.add(t.val);
            if(t.left!=null) {
                queue.add(t.left);
            }
            if(t.right!=null) {
                queue.add(t.right);
            }
        }
        //此时的tmp就是当前层的全部元素,用List类型的tmp保存,加入最终结果集中
        res.add(tmp);
    }
    return res;
    }
}

层次遍历------自底向上版

java 复制代码
    3
   / \
  9  20
    /  \
   15   7
输出结果
[
  [15,7],
  [9,20],
  [3]
]

从下到上输出每一层的节点值,只要对上述操作稍作修改即可,在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部。

为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构,在链表头部添加一层节点值的列表的时间复杂度是 O(1)。在 Java 中,由于我们需要返回的 List 是一个接口,这里可以使用链表实现。

java 复制代码
public List<List<Integer>> levelOrderBottom(TreeNode root) {
    List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
    if (root == null) {
        return levelOrder;
    }
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        List<Integer> level = new ArrayList<Integer>();
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            TreeNode left = node.left, right = node.right;
            if (left != null) {
                queue.offer(left);
            }
            if (right != null) {
                queue.offer(right);
            }
        }
        levelOrder.add(0, level);//栈
    }
    return levelOrder;
}

二叉树的锯齿形层次遍历

java 复制代码
二叉树:[3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其层序遍历结果:
 [
  [3],
  [20,9],
  [15,7]
]

为了满足题目要求的返回值为「先从左往右,再从右往左」交替输出的锯齿形,可以利用「双端队列」的数据结构来维护当前层节点值输出的顺序。双端队列是一个可以在队列任意一端插入元素的队列。在广度优先搜索遍历当前层节点拓展下一层节点的时候我们仍然从左往右按顺序拓展,但是对当前层节点的存储我们维护一个变量 isOrderLeft 记录是从左至右还是从右至左的:

  • 如果从左至右,我们每次将被遍历到的元素插入至双端队列的末尾。

  • 从右至左,我们每次将被遍历到的元素插入至双端队列的头部。

java 复制代码
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    if (root == null) {
        return ans;
    }
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.offer(root);
    boolean isOrderLeft = true;
    while (!queue.isEmpty()) {
        Deque<Integer> levelList = new LinkedList<Integer>();
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            TreeNode curNode = queue.poll();
            if (isOrderLeft) {
                levelList.offerLast(curNode.val);
            } else {
                levelList.offerFirst(curNode.val);
            }
            if (curNode.left != null) {
                queue.offer(curNode.left);
            }
            if (curNode.right != null) {
                queue.offer(curNode.right);
            }
        }
        ans.add(new LinkedList<Integer>(levelList));
        isOrderLeft = !isOrderLeft;
    }
    return ans;
}

N叉树的层次遍历

java 复制代码
输入:root = [1,null,3,2,4,null,5,6](表述树的元素是这个序列)
输出:[[1],[3,2,4],[5,6]]
N叉树的定义如下,就是一个值,加一个列表,其类型仍然是Node:
class Node {
    public int val;
    public List<Node> children;
}
java 复制代码
public List<List<Integer>> nLevelOrder(Node root) {
    List<List<Integer>> value = new ArrayList<>();
    Deque<Node> q = new ArrayDeque<>();
    if (root != null)
        q.addLast(root);
    while (!q.isEmpty()) {
        Deque<Node> next = new ArrayDeque<>();
        List<Integer> nd = new ArrayList<>();
        while (!q.isEmpty()) {
            Node cur = q.pollFirst();
            nd.add(cur.val);
            for (Node chd : cur.children) {
                if (chd != null)
                    next.add(chd);
            }
        }
        q = next;
        value.add(nd);
    }
    return value;
}

处理每层元素的题目

在每个树行找最大值

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

java 复制代码
public List<Integer> largestValues(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    Deque<TreeNode> deque = new ArrayDeque<>();
    
    if (root != null) {
        deque.addLast(root);
    }
    
    while (!deque.isEmpty()) {
        int size = deque.size();
        int levelMaxNum = Integer.MIN_VALUE;
        for (int i = 0; i < size; i++) {
            TreeNode node = deque.poll();
            levelMaxNum = Math.max(node.val,levelMaxNum);
            if (node.left != null) deque.addLast(node.left);
            if (node.right != null) deque.addLast(node.right);
        }
        res.add(levelMaxNum);
    }
    return res;
}

在每个树行中找平均值

要求给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

java 复制代码
public List<Double> averageOfLevels(TreeNode root) {
    List<Double> res = new ArrayList<>();
    if (root == null) return res;
    Queue<TreeNode> list = new LinkedList<>();
    list.add(root);
    while (list.size() != 0){
        int len = list.size();
        double sum = 0;
        for (int i = 0; i < len; i++){
            TreeNode node = list.poll();
            sum += node.val;
            if (node.left != null) list.add(node.left);
            if (node.right != null) list.add(node.right);
        }
        res.add(sum/len);
    }
    return res;
}

二叉树的右视图

给定一个二叉树的根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

java 复制代码
public List<Integer> rightSideView(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) {
        return res;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (i == size - 1) {  //将当前层的最后一个节点放入结果列表
                res.add(node.val);
            }
        }
    }
    return res;
}

最底层最左边

二叉树最底层最左边的值的问题:给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

java 复制代码
示例1:
输入: root = [2,1,3]
输出: 1

示例2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

我们可以发现,正常执行层次遍历,不管最底层有几个元素,最后一个输出的一定是是最底层最右的元素7,那这里我们就想了,将该处理与上一次题的翻转结合一下,每一层都是先反转再放入队列,就可以让最后一个输出的是最左的。

java 复制代码
public int findBottomLeftValue(TreeNode root) {
    if (root.left == null && root.right == null) {
        return root.val;
    }
    
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    TreeNode temp = new TreeNode(-100);
    
    while (!queue.isEmpty()) {
        temp = queue.poll();
        if (temp.right != null) {
            // 先把右节点加入 queue
            queue.offer(temp.right);
        }
        if (temp.left != null) {
            // 再把左节点加入 queue
            queue.offer(temp.left);
        }
    }
    return temp.val;
}
相关推荐
ElseWhereR40 分钟前
合并两个有序链表 - 简单
数据结构·链表
yuhao__z3 小时前
代码随想录算法训练营第五十六天| 图论2—卡码网99. 岛屿数量(dfs & bfs)
算法·深度优先·图论
小wanga4 小时前
【算法专题十】哈希表
算法·哈希算法·散列表
S01d13r6 小时前
LeetCode 解题思路 45(分割等和子集、最长有效括号)
算法·leetcode·职场和发展
理想奋斗中8 小时前
【LeetCode Hot100 | 每日刷题】二叉树的层序遍历
算法·leetcode·bfs
阳洞洞8 小时前
leetcode 24. 两两交换链表中的节点
数据结构·leetcode·链表
JCBP_9 小时前
C++(1)
开发语言·c++·算法
数据与人工智能律师9 小时前
互联网法院在NFT、元宇宙等新兴领域的规则创新
大数据·网络·人工智能·算法·区块链
知识漫步9 小时前
代码随想录算法训练营第60期第二十八天打卡
算法
chao_78910 小时前
手撕算法(1)
算法