2023-05-16 刷题

LeetCode 662. 二叉树最大宽度

思路:节点编号 + BFS。时间复杂度:O(n),空间复杂度:O(n)

  1. 利用完全二叉树(以及堆的顺序存储)编号的规则,根节点编号为1,左孩子是2,右孩子是3. 对于任意一个节点,如果它的编号是i, 那么它的左孩子的编号是2*i, 右孩子的编号是2*i + 1. 因为有的节点没有左右孩子,因此,需要把编号和节点保存在一起。可以使用Pair结构。

  2. 每层节点最大宽度是BFS逐层遍历得到的节点列表中,最右边的编号 - 最左边的编号 + 1。 所以这里可以使用双端队列Deque这个接口,和ArrayDeque这个队列实现类。

  3. BFS有两种写法,一种是一层while循环,一次处理一个节点。另外一种是两层循环,内层循环遍历当前一层。外层循环,对应二叉树的层数(高度)。显然,这个题目适合用逐层遍历的写法。

  4. 分析复杂度:二叉树中的每个节点进入队列一次,出队列一次,所以总共遍历2n次,时间负责度是O(n). BFS 遍历保存的队列,最多的情况是叶子那一层,节点个数超过 n/2(满二叉树下,它是n/2上取整),因此空间复杂度是 O(n).

class Solution {
    public int widthOfBinaryTree(TreeNode root) {
        Deque<NodePair> que = new ArrayDeque<>();
        que.offer(new NodePair(root, 1));
        int ans = 1;
        while (!que.isEmpty()) {
            int curWidth = que.getLast().index - que.getFirst().index + 1;
            ans = Math.max(ans, curWidth);

            for (int i = que.size(); i > 0; i--) {
                NodePair cur = que.poll();
                int left = cur.index * 2;
                TreeNode node = cur.node;
                if (node.left != null) {
                    que.offer(new NodePair(node.left, left));                    
                }
                if (node.right != null) {
                    que.offer(new NodePair(node.right, left + 1));
                }
            }
        }
        return ans;
    }

    static class NodePair {
        TreeNode node;
        int index;
        public NodePair(TreeNode node, int index) {
            this.node = node;
            this.index = index;
        }
    }
}

5-17 更新:另一种思路,DFS实现

这个题目竟然还可以通过DFS来做。只要确定了使用编号的思想,那么遍历除了BFS,还应该想到DFS。
问题转换成,在DFS的情况下,怎么知道一层的最左和最右的节点?

  • DFS遍历时某一层的情况,其实也是从左到右。不过是穿插着进行的。
  • 可以使用一个Map记录每一层的最左边的节点。第一次添加到这一层的时,一定是最左节点。
  • 对于某层的最右节点,我们可以在遍历时,直接计算width,然后返回最大结果(或者更新全局最大值)
    代码如下:
class Solution {
    Map<Integer, Integer> levelMap;

    public int widthOfBinaryTree(TreeNode root) {
        levelMap = new HashMap<>();
        return dfs(root, 1, 1);
    }

    int dfs(TreeNode root, int level, int id) {
        if (root == null) {
            return 0;
        }
        levelMap.putIfAbsent(level, id); // 第一次遍历某一层第一个节点一定是该层最左节点
        
        int leftAns = dfs(root.left, level + 1, id * 2);
        int rightAns = dfs(root.right, level + 1, id * 2 + 1);
        int curAns = id - levelMap.get(level) + 1;
        return Math.max(curAns, Math.max(leftAns, rightAns));
    }
}

复杂度分析

  • 时间:O(n)每个节点只遍历一遍。
  • 空间:O(n) Map最大保存n个元素,栈最大的深度也是n(当二叉树退化成单枝的情况下)

DFS实现二叉树层次遍历

所以同样地,我们可以使用DFS来实现二叉树的层次遍历。

import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

public class BinaryTreeDFS {
    public static List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        dfs(root, 0, ans); // for convenient, level begins with 0
        return ans;
    }

    static void dfs(TreeNode root, int level, List<List<Integer>> ans) {
        if (root != null) {
            // when visit the leftmost node of one level, add placeholder list
            if (level == ans.size()) {
                ans.add(new ArrayList<>());
            }
            ans.get(level).add(root.val);

            dfs(root.left, level + 1, ans);
            dfs(root.right, level + 1, ans);
        }
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(9);
        root.right = new TreeNode(20);
        
        root.left.left = new TreeNode(1);
        root.right.left = new TreeNode(15);
        root.right.right = new TreeNode(7);

        List<List<Integer>> levelOrderTraversal = levelOrder(root);
        for (List<Integer> level : levelOrderTraversal) {
            System.out.println(level);
        }
    }
}
// time: O(n),每个节点都只遍历一次。
// space: O(n) 使用Map最多保存n个元素,栈最大深度也是n。(当二叉树退化成单支时,即每个节点只有一个孩子)

收获总结

  1. 学习到在BFS做层次遍历时,除了最常见的保存TreeNode到队列中,还可以保存更多的信息,例如节点编号
  2. 看到中文官方题解,给出的BFS实现,没有使用Queue,而是使用一个List记录当前层,一个临时List记录下一层,然后在外层循环更新当前层的List。
  3. 学会用DFS来做层次遍历的思想
posted @ 2023-05-17 21:45  编程爱好者-java  阅读(13)  评论(0编辑  收藏  举报