【代码随想录训练营】Day16-二叉树

代码随想录训练营 Day16

今日任务

104.二叉树的最大深度
559.N叉树的最大深度
111.二叉树的最小深度
222.完全二叉树的节点个数
语言:Java

理论基础

节点的高度:节点到最近叶子节点的距离,后序遍历
节点的深度:根节点到节点的距离,前序遍历
根节点的最大高度就是树的最大深度
根节点的最小高度就是树的最小深度
所以可以用后序遍历求树的最大/小深度

104. 二叉树的最大深度

链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree/
后序遍历递归

/**
 * Definition for a binary tree node.
 * public 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 int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

前序遍历回溯
现在理解的还不太透彻,先写上

class Solution {
    int result;
    public void getDepth(TreeNode node, int depth){
        result = result > depth ? result : depth;
        if(node.left == null && node.right == null){
            return;
        }
        if(node.left != null){
            depth++;
            getDepth(node.left, depth);
            depth--;
        }
        if(node.right != null){
            depth++;
            getDepth(node.right, depth);
            depth--;
        }
    }
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        result = 0;
        getDepth(root, 1);
        return result;
    }
}

层序遍历迭代

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        int result = 0;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            result++;
            int size = queue.size();
            for(int i = 0; i < size; i++){
                TreeNode cur = queue.poll();
                if(cur.left != null){
                    queue.offer(cur.left);
                }
                if(cur.right != null){
                    queue.offer(cur.right);
                }
            }
        }
        return result;
    }
}

559. N叉树的最大深度

链接:https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/
后序遍历递归

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public int maxDepth(Node root) {
        if(root == null){
            return 0;
        }
        List<Node> children = root.children;
        int curDepth = 0;
        while(!children.isEmpty()){
            curDepth = Math.max(maxDepth(children.get(0)), curDepth);
            children.remove(0);
        }
        return curDepth + 1;
    }
}

层序遍历迭代

class Solution {
    public int maxDepth(Node root) {
        if(root == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(root);
        int result = 0;
        while(!queue.isEmpty()){
            result++;
            int size1 = queue.size();
            for(int i = 0; i < size1; i++){
                Node cur = queue.poll();
                List<Node> children = cur.children;
                int size2 = children.size();
                for(int j = 0; j < size2; j++){
                    queue.offer(children.get(0));
                    children.remove(0);
                }
            }
        }
        return result;
    }
}

111. 二叉树的最小深度

链接:https://leetcode.cn/problems/minimum-depth-of-binary-tree/
后序遍历递归

/**
 * Definition for a binary tree node.
 * public 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 int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        int leftDepth = minDepth(root.left); //左
        int rightDepth = minDepth(root.right); //右
        //必须写下面的if判断,否则会将没有左孩子/右孩子的非叶子节点当作最低层
        if(root.left == null && root.right != null){
            return rightDepth + 1;
        }
        else if(root.left != null && root.right == null){
            return leftDepth + 1;
        }
        return Math.min(leftDepth, rightDepth) + 1; //根
    }
}

前序遍历回溯

class Solution {
    int result;
    public void getDepth(TreeNode node, int depth){
        if(node.left == null && node.right == null){
            result = result < depth ? result : depth;
            return;
        }
        if(node.left != null){
            depth++;
            getDepth(node.left, depth);
            depth--;
        }
        if(node.right != null){
            depth++;
            getDepth(node.right, depth);
            depth--;
        }
    }
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        result = Integer.MAX_VALUE;
        getDepth(root, 1);
        return result;
    }
}

层序遍历迭代

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        int result = 0;
        while(!queue.isEmpty()){
            result++;
            int size = queue.size();
            for(int i = 0; i < size; i++){
                TreeNode cur = queue.poll();
                if(cur.left == null && cur.right == null){
                    return result;
                }
                if(cur.left != null){
                    queue.offer(cur.left);
                }
                if(cur.right != null){
                    queue.offer(cur.right);
                }
            }
        }
        return result;
    }
}

222. 完全二叉树的节点个数

链接:https://leetcode.cn/problems/count-complete-tree-nodes/
后序遍历递归

/**
 * Definition for a binary tree node.
 * public 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 int countNodes(TreeNode root) {
        if(root == null){
            return 0;
        }
        int leftNodes = countNodes(root.left);
        int rightNodes = countNodes(root.right);
        return leftNodes + rightNodes + 1;
    }
}

时间复杂度:O(N)
空间复杂度:O(logN),每次递归都是向下走一层,栈最多需要的空间等于二叉树的高度

层序遍历迭代

class Solution {
    public int countNodes(TreeNode root) {
        if(root == null){
            return 0;
        }
        int result = 0;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        result++;
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            if(cur.left != null){
                queue.offer(cur.left);
                result++;
            }
            if(cur.right != null){
                queue.offer(cur.right);
                result++;
            }
        }
        return result;
    }
}

时间复杂度:O(N)
空间复杂度:O(N)

针对完全二叉树的递归
完全二叉树的节点有两种情况:
① 满二叉树:用 2^n - 1 计算
② 最后一层叶子节点没有满:可以通过继续递归得到最终满二叉树
满二叉树:向左遍历的深度等于向右遍历的深度,如果不相等,则继续递归;
计算满二叉树的总节点个数不需要知道这个节点具体在第几层,只需要知道节点当前所在的满二叉树有几层即可,所以每次递归时 leftDepth 和 rightDepth 都从头开始计算即可。

class Solution {
    public int countNodes(TreeNode root) {
        if(root == null){
            return 0;
        }
        TreeNode left = root.left;
        TreeNode right = root.right;
        int leftDepth = 1, rightDepth = 1;
        while(left != null){
            leftDepth++;
            left = left.left;
        }
        while(right != null){
            rightDepth++;
            right = right.right;
        }
        //满二叉树
        if(leftDepth == rightDepth){
            return (1 << leftDepth) - 1;
        }
        //最后一层叶子节点没有满, 继续递归
        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}

时间复杂度:O(logN × logN)(我的理解:如果需要继续递归,则每一层都要进行一次寻找到最左/右的遍历,遍历一次时间复杂度是O(logN),一共有O(logN)层)
空间复杂度:O(logN)

二分查找+位运算
很妙的解法,要充分利用完全二叉树的性质
关于判断二分查找计算mid时该向上取整还是向下取整的小技巧:
就以0和1为例,left=0, right=1,进入循环判断,将计算出的mid = 0 / mid = 1 代入 if 和 else 分别验证,如果下一次的 left 和 right 无法满足循环退出条件,说明会陷入死循环,需要调整取整方式。
以左闭右闭解法为例:如果是向下取整,mid = 0,if 分支下一次left=0, right=1,不满足循环退出条件,else分支下一次left=0, right=0,满足循环退出条件;如果是向上取整,mid = 1,if 分支下一次left=1, right=1,满足循环退出条件,else 分支下一次left=0, right=0,满足循环退出条件;所以应该向上取整。
以左闭右开向下取整为例:mid = 0,if 分支下一次left=0, right=1,满足循环退出条件,else 分支下一次left=0, right=0,满足循环退出条件;mid = 1,if 分支下一次left=1, right=1,满足循环退出条件,else 分支下一次 left=0, right=1,满足循环退出条件。

class Solution {
    public boolean exist(TreeNode node, int num, int depth){
        //利用完全二叉树的特点
        //12: 1100,对应二叉树的路径:根节点-右-左-左
        int bits = 1 << (depth - 1); //因为是1左移,所以与操作得到的就是该位本身的值
        TreeNode cur = node;
        while(bits > 0 && cur != null){
            //& 优先级小于 !=
            if((bits & num) != 0){
                cur = cur.right; //1就向右走
            }
            else{
                cur = cur.left; //0就向左走
            }
            bits >>= 1;
        }
        return cur != null;
    }
    public int countNodes(TreeNode root) {
        if(root == null){
            return 0;
        }
        int depth = 0;
        TreeNode cur = root;
        while(cur.left != null){
            depth++;
            cur = cur.left;
        }
        //左闭右闭
        int left = 1 << depth; //最后一层最左元素编号是 2^depth
        int right = (1 << (depth + 1)) - 1; //最右一层最右元素编号是 2^(depth + 1) - 1
        while(left < right){
            int mid = left + ((right - left + 1) >> 1);
            //mid对应元素存在
            if(exist(root, mid, depth)){
                left = mid;
            }
            else{
                right = mid - 1;
            }
        }
        return left; //right也可
        
	    //左闭右开写法
        // int left = 1 << depth; //最后一层最左元素编号是 2^depth
        // int right = (1 << (depth + 1)); //最右一层最右元素编号是 2^(depth + 1) - 1
        // while(left < right - 1){
        //     int mid = left + ((right - left) >> 1); //向下取整和向上取整都可以
        //     //mid对应元素存在
        //     if(exist(root, mid, depth)){
        //         left = mid;
        //     }
        //     else{
        //         right = mid;
        //     }
        // }
        // return left; //right-1 也可
    }
}

时间复杂度:O(logN × logN)(最后一层有 2^depth 个元素,二分查找的时间复杂度是 O(log2^depth) = O(depth) = O(logN),而每个元素都要进行一次路径的遍历,遍历一次的时间复杂度是O(logN),合在一起就是O(logN × logN))
空间复杂度:O(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值