二叉树高频题(不含dp)

文章介绍了二叉树的各种遍历方法,如层序、锯齿形层序、最大特殊宽度的计算,以及与之相关的深度、序列化与反序列化、完全二叉树节点个数和最近公共祖先等问题,涉及递归和全局变量的使用。
摘要由CSDN通过智能技术生成

二叉树的层序遍历

测试链接 : . - 力扣(LeetCode)

class Solution {
    public static TreeNode[] queue = new TreeNode[2001];
    public List<List<Integer>> levelOrder(TreeNode root) {
        // 利用队列
        int l, r;
        List<List<Integer>> ans = new ArrayList<>();
        if (root != null) {
            l = r = 0;
            queue[r++] = root;
            while (l < r) { // 队列里还有东西
                int size = r - l;
                List<Integer> list = new ArrayList<>();
                for (int i = 0; i < size; i++) {
                    TreeNode cur = queue[l++];
                    list.add(cur.val);
                    if (cur.left != null) {
                        queue[r++] = cur.left;
                    }
                    if (cur.right != null) {
                        queue[r++] = cur.right;
                    }
                }
                ans.add(list);
            }
        }
        return ans;
    }
}

二叉树的锯齿形层序遍历

测试链接 : . - 力扣(LeetCode)

与层序遍历的区别就是多了一个调头的操作

class Solution {
    public static TreeNode[] queue = new TreeNode[2001];

    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        int l, r;
        if (root != null) {
            l = r = 0;
            queue[r++] = root;
            // false 左到右
            // true 右到左
            boolean reverse = false;
            while (l < r) {
                List<Integer> list = new ArrayList<>();
                int size = r - l;
                // 左到右 l.....r-1
                // 右到左 r-1...l
                for (int i = reverse ? r - 1 : l, j = reverse ? -1 : 1, k = 0; k < size; k++, i += j) {
                    list.add(queue[i].val);
                }
                for (int i = 0; i < size; i++) {
                    TreeNode cur = queue[l++];
                    if (cur.left != null)
                        queue[r++] = cur.left;
                    if (cur.right != null)
                        queue[r++] = cur.right;
                }
                ans.add(list);
                reverse = !reverse;
            }
        }
        return ans;
    }
}

二叉树的最大特殊宽度

测试链接 : . - 力扣(LeetCode)

每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点,这些 null 节点也计入长度。

class Solution {
    public static TreeNode[] treeQueue = new TreeNode[3001];
    public static int[] indexQueue = new int[3001];

    public int widthOfBinaryTree(TreeNode root) {
        // 记录下对应的坐标就能很好的求每一层的宽度
        int l, r;
        int max = 0;
        if (root != null) {
            l = r = 0;
            treeQueue[r] = root;
            indexQueue[r++] = 1;
            while (l < r) {
                int size = r - l;
                // 取每一层的第一个和最后一个值
                // l.....r-1
                max = Math.max(max, indexQueue[r - 1] - indexQueue[l] + 1);
                for (int i = 0; i < size; i++) {
                    TreeNode cur = treeQueue[l];
                    int index = indexQueue[l++];
                    if (cur.left != null) {
                        treeQueue[r] = cur.left;
                        indexQueue[r++] = index * 2;
                    }
                    if (cur.right != null) {
                        treeQueue[r] = cur.right;
                        indexQueue[r++] = index * 2 + 1;
                    }
                }
            }
        }
        return max;
    }
}

求二叉树的最大、最小深度

最大深度 : . - 力扣(LeetCode)

class Solution {
    public int maxDepth(TreeNode root) {
        //自底向上数所以要算上自身 +1 
        return root == null ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

最小深度 : . - 力扣(LeetCode)

class Solution {
    public int minDepth(TreeNode root) {
        //自底向上
        //先考虑最底部会出现什么情况
        if(root == null)    return 0;
        if(root.left ==null && root.right == null)  return 1;
        int ldeep = Integer.MAX_VALUE;
        int rdeep = Integer.MAX_VALUE;
        if(root.left != null)   ldeep = minDepth(root.left);
        if(root.right !=null)   rdeep = minDepth(root.right);
        return Math.min(ldeep,rdeep)+1;
    }
}

二叉树先序序列化和反序列化

测试链接 : . - 力扣(LeetCode)

// 二叉树可以通过先序、后序或者按层遍历的方式序列化和反序列化
// 但是,二叉树无法通过中序遍历的方式实现序列化和反序列化
// 因为不同的两棵树,可能得到同样的中序序列,即便补了空位置也可能一样。
// 比如如下两棵树
//         __2
//        /
//       1
//       和
//       1__
//          \
//           2
// 补足空位置的中序遍历结果都是{ null, 1, null, 2, null}
public class Codec {
    //序列化
    public String serialize(TreeNode root) {
        StringBuilder builder = new StringBuilder();
        s(root, builder);
        return builder.toString();
    }

    public void s(TreeNode root, StringBuilder builder) {
        if (root == null) {
            builder.append("#,");
        } else {
            builder.append(root.val + ",");
            s(root.left, builder);
            s(root.right, builder);
        }
    }
    //反序列化
    public TreeNode deserialize(String data) {
        String[] vals = data.split(",");
        cnt=0;
        return g(vals);
    }

    // 当前数组消费到哪了
    public static int cnt;

    TreeNode g(String[] vals) {
        //自顶向下
        String cur = vals[cnt++];
        if (cur.equals("#")) {
            return null;
        } else {
            TreeNode head = new TreeNode(Integer.valueOf(cur));
            head.left = g(vals);
            head.right = g(vals);
            return head;
        }
    }
}

二叉树按层序列化和反序列化

测试链接 : . - 力扣(LeetCode)

public class Codec {
    // 层序遍历需要队列
    // 其实也就是让你去层序遍历一下二叉树并记录
    public static int MAX = 10001;

    public static TreeNode[] queue = new TreeNode[MAX];

    public String serialize(TreeNode root) {
        int l, r;
        StringBuilder builder = new StringBuilder();
        if (root != null) {
            l = r = 0;
            builder.append(root.val + ",");
            queue[r++] = root;
            while (l < r) {
                // 取出队列第一个元素也就是本层第一个元素
                root = queue[l++];
                // 这道题不用再分一层一层,就是求个size用for循环,拿到数据直接将左右子树放入队列就行
                if (root.left != null) {
                    builder.append(root.left.val + ",");
                    queue[r++] = root.left;
                } else {
                    // 统一规定 空值用#表示
                    builder.append("#,");
                }
                if (root.right != null) {
                    builder.append(root.right.val + ",");
                    queue[r++] = root.right;
                } else {
                    builder.append("#,");
                }
            }
        }
        return builder.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.equals("")) {
            return null;
        }
        String[] s = data.split(",");
        int l = 0, r = 0;
        int index = 0;
        //队列的作用是承载有值的节点
        //先取出第一个值并放入队列中
        TreeNode root = generate(s[index++]);
        queue[r++] = root;
        while(l<r){
            TreeNode cur = queue[l++];
            cur.left = generate(s[index++]);
            if(cur.left!=null){
                queue[r++] = cur.left;
            }
            cur.right = generate(s[index++]);
            if(cur.right!=null){
                queue[r++] = cur.right;
            }
        }
        return root;
    }
    public TreeNode generate(String s){
        return s.equals("#") ?  null :  new TreeNode(Integer.valueOf(s));
    }

利用先序与中序遍历序列构造二叉树

测试链接 : . - 力扣(LeetCode)

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        // 先序第一个节点一定是头
        // 根据中序划分出范围 左边的都是左子树 右边的都是右子树
        if (preorder == null || inorder == null || preorder.length != inorder.length) {
            return null;
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        //快速的找到中序序列中值对应的坐标
        //不可能每次去找中序位置的时候都遍历一遍数组,这里就想到用hashmap先记录下来
        for (int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, map);
    }

    public static TreeNode build(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2,
            HashMap<Integer, Integer> map) {
        //自顶向下 返回节点 再连接左右子树
        if (l1 > r1) {
            return null;
        }
        TreeNode node = new TreeNode(preorder[l1]);
        if (r1 == l1) {
            return node;
        }
        int k = map.get(preorder[l1]);
        // l1.....).....r1
        // l2....k.....r2
        node.left = build(preorder, l1 + 1, l1 + k - l2, inorder, l2, k - 1, map);
        node.right = build(preorder, l1+k-l2+1, r1, inorder, k + 1, r2, map);
        return node;
    }
}

后序与中序遍历与这个类似,最后一个一定是头节点,然后再去中序找左右子树

验证完全二叉树

测试链接 : . - 力扣(LeetCode)

class Solution {
    //进行层序遍历,需要队列
    public static int MAX = 101;
    public static TreeNode queue[] = new TreeNode[MAX];
    public static int l,r;
    public boolean isCompleteTree(TreeNode root) {
        if(root == null){
            return true;
        }
        l = r = 0;
        queue[r++] = root;
        //碰到了叶子节点或者只有左子树的节点了,之后的节点都必须是叶子节点
        boolean isleaf = false;
        while(l < r){
            root = queue[l++];
            //1、如果只有右子树直接返回false
            //2、如果前面有过不双全的节点了(叶子节点或者只有左子树)之后的都必须是叶子节点
            if((root.left==null&&root.right!=null) || (isleaf&&(root.left!=null||root.right!=null))){
                return false;
            }
            if(root.left!=null){
                queue[r++] = root.left;
            }
            if(root.right!=null){
                queue[r++] = root.right;
            }
            //碰到叶子节点或者只有左子树的节点记录下来
            //root.left == null && root.right == null
            //root.left != null && root.right ==null
            //合并一下只要右子树是null就记录
            if(root.right==null){
                isleaf = true;
            }
        }
        return true;
    }
}

求完全二叉树的节点个数

测试链接 : . - 力扣(LeetCode)

完全二叉树:

  • 除了最底层以外,其余的每一层节点数都是满的

首先需要明确完全二叉树的定义:它是一棵空树或者它的叶子节点只出在最后两层,若最后一层不满则叶子节点只在最左侧。

再来回顾一下满二叉的节点个数怎么计算,如果满二叉树的层数为h,则总节点数为:2^h - 1.

那么我们来对 root 节点的左右子树进行高度统计,分别记为 left 和 right,有以下两种结果:

  • left == right。这说明,左子树一定是满二叉树,因为节点已经填充到右子树了,左子树必定已经填满了。所以左子树的节点总数我们可以直接得到,是 2^left - 1,加上当前这个 root 节点,则正好是 2^left。再对右子树进行递归统计。
  • left != right。说明此时最后一层不满,但倒数第二层已经满了,可以直接得到右子树的节点个数。同理,右子树节点 +root 节点,总数为 2^right。再对左子树进行递归查找。
class Solution {
    public int countNodes(TreeNode root) {
        if(root==null)  return 0;
        int left = countLevel(root.left);
        int right = countLevel(root.right);
        if(left == right){
            return countNodes(root.right) + (1<<left);
        }else{
            return countNodes(root.left) + (1<<right);
        }
    }
    public static int countLevel(TreeNode root){
        int level = 0;
        while(root!=null){
            level++;
            root = root.left;
        }
        return level;
    }
}

普通二叉树上寻找两个节点的最近公共祖先

测试链接:. - 力扣(LeetCode)

深度优先搜素

考虑通过递归对二叉树进行先序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p,q 在节点 root的异侧时,节点 root即为最近公共祖先,则向上返回 root;

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root ==  null|| root == p || root ==q) return root;
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left == null && right == null)   return null;
        if(left==null)  return right;
        if(right==null) return left;
        return root;
    }
}

搜索二叉树上寻找两个节点的最近公共祖先

测试链接 : . - 力扣(LeetCode)

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// root从上到下
// 如果先遇到了p,说明p是答案
// 如果先遇到了q,说明q是答案
// 如果root在p~q的值之间,不用管p和q谁大谁小,只要root在中间,那么此时的root就是答案
// 如果root在p~q的值的左侧,那么root往右移动
// 如果root在p~q的值的右侧,那么root往左移动
        while (root != null) {
            if (root.val < p.val && root.val < q.val)
                root = root.right;
            else if (root.val > p.val && root.val > q.val)
                root = root.left;
            else break;
        }
        return root;
    }
}

收集累加和等于aim的所有路径

测试链接 : . - 力扣(LeetCode)

本题是典型的回溯问题,解法包含先序遍历 + 路径记录两部分:

设计到还原递归状态

class Solution {
    LinkedList<List<Integer>> ans = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        recur(root, targetSum);
        return ans;
    }

    public void recur(TreeNode root, int targetSum) {
         //终止条件: 若节点 root 为空,则直接返回
        if (root == null)
            return;
        // 将节点加入path
        path.add(root.val);
        // targetsum-root.val
        targetSum -= root.val;
        if (targetSum == 0 && root.left == null && root.right == null) {
            // 找到了路径放入ans
            ans.add(new LinkedList<Integer>(path));
        }
        recur(root.left, targetSum);
        recur(root.right, targetSum);
        //还原
        path.removeLast();
    }
}

验证平衡二叉树

测试链接 : . - 力扣(LeetCode)

思路:. - 力扣(LeetCode)

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root) != -1;
    }

    public int height(TreeNode root) {
        if (root == null)
            return 0;
        int lh = height(root.left);
        //当深度为-1时代表,左(右)子树不平衡 返回-1,会直接返回给上层 剪枝
        if (lh == -1)
            return -1;
        int rh = height(root.right);
        if (rh == -1)
            return -1;
        return Math.abs(lh - rh) < 2 ? Math.max(lh, rh) + 1 : -1;
    }
}

验证搜索二叉树

测试链接 : . - 力扣(LeetCode)

递归

如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树

class Solution {
    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public boolean isValidBST(TreeNode root, Long lower, Long upper) {
        if (root == null)
            return true;
        if (root.val <= lower || root.val >= upper)
            return false;
        return isValidBST(root.left, lower, Long.valueOf(root.val)) && isValidBST(root.right, Long.valueOf(root.val), upper);
    }
}

全局变量法

class Solution {
    //记录子树的最小和最大值
    public static long min, max;

    public boolean isValidBST(TreeNode head) {
        // 当前节点 大于左子树的最大值 小于右子树的最小值
        // 自底向上,可以记录下最大最小值
        if (head == null) {
            min = Long.MAX_VALUE;
            max = Long.MIN_VALUE;
            return true;
        }
        boolean lok = isValidBST(head.left);
        long lmin = min;
        long lmax = max;
        boolean rok = isValidBST(head.right);
        long rmin = min;
        long rmax = max;
        min = Math.min(Math.min(lmin, rmin), head.val);
        max = Math.max(Math.max(lmax, rmax), head.val);
        return lok && rok && lmax < head.val && head.val < rmin;
    }
}

修剪搜索二叉树

测试链接 : . - 力扣(LeetCode)

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        // 自顶向下
        if (root == null) {
            return null;
        }
        if (root.val < low) {
            return trimBST(root.right, low, high);
        }
        if (root.val > high) {
            return trimBST(root.left, low, high);
        }
        root.left = trimBST(root.left, low, high);
        root.right = trimBST(root.right, low, high);
        return root;
    }
}

二叉树打家劫舍问题

测试链接 : . - 力扣(LeetCode)

全局变量法(有一点dp的感觉)

class Solution {
    // 全局变量,完成了X子树的遍历,返回之后
    // yes变成,X子树在偷头节点的情况下,最大的收益
    public static int yes;

    // 全局变量,完成了X子树的遍历,返回之后
    // no变成,X子树在不偷头节点的情况下,最大的收益
    public static int no;

    public int rob(TreeNode root) {
        f(root);
        return Math.max(yes, no);
    }

    public static void f(TreeNode root) {
        if (root == null) {
            yes = 0;
            no = 0;
        } else {
            int y = root.val;
            int n = 0;
            f(root.left);
            //此时全局变量记录的是左子树的最大收益
            y += no;
            n += Math.max(yes, no);
            f(root.right);
            y += no;
            n += Math.max(yes, no);
            yes = y;
            no = n;
        }
    }
}

  • 37
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值