算法刷题 - 二叉树(最小深度+求节点总数 + 平衡二叉树+二叉树的所有路径+左叶子之和)

82 阅读8分钟

简介

题目 01: 最小二叉树深度 (LeetCode 111)

题目 02: 完全二叉树的节点个数(LeetCode 222)

题目 03: 判断是否是平衡二叉树(LeetCode 110)

题目 04: 求二叉树的所有路径(LeetCode 257)

题目 05: 求二叉树左叶子节点之和(LeetCode 404)

算法刷题博客封面.png

题目 01 - 求二叉树的最小深度

原题链接

LeetCode 111 - 最小二叉树深度

题目描述

给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

思路

  • 借助队列实现二叉树层序遍历
  • 层序遍历,当遍历到左右节点都为空的时候;即找到了当前树的叶子节点,得到最小深度

代码实现

class Solution {

    public int minDepth(TreeNode root) {
        if (root == null)
            return 0;
        if (root.left == null && root.right == null) {
            return 1;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        // 根节点入队
        queue.offer(root);
        // 记录最小深度; 起始根节点深度为 1
        int depth = 0;
        while (!queue.isEmpty()) {
            // 还存在下一层树节点
            depth++;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.poll();
                //遍历下一层
                if (peek.left != null) {
                    queue.offer(peek.left);
                }
                if (peek.right != null) {
                    queue.offer(peek.right);
                }
                // 左右子树都为空,遍历到叶子节点; 达到最大深度
                if (peek.left == null && peek.right == null) {
                    return depth;
                }
            }
        }
        throw new RuntimeException("Not such element!");
    }
}

执行结果

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

题目 02 - 完全二叉树的节点个数

原题链接

LeetCode 222 - 最小二叉树深度

题目描述

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

若最底层为第 h 层,则该层包含 1~ 2h 个节点。

思路 - 递归

递归

  • 递归终止条件: 当前节点为空,返回 0
  • 每层逻辑: 获取左子树节点个数 leftNum 和右子树 rightNum; 返回当前层根节点+左右子树总数 leftNum + rightNum + 1
  • 叶子节点 leftNumrightNum 皆为 0; 返回 1 自身

代码实现

class Solution {
    public int countNodes(TreeNode root) {
        if(root == null) {
            return 0;
        }
        //左子树的节点数目;叶子节点为 0
        int leftNum = countNodes(root.left);
        //左子树的节点数目;叶子节点为 0
        int rightNum = countNodes(root.right);
        return leftNum + rightNum + 1;  //返回上层
    }
}

执行结果

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

思路 - 迭代

迭代(层序遍历)

  • 借助队列完成每一层树的遍历
  • 每一层的树节点总数可以通过队列的 size() 获取
  • 累加各层树节点总数目

代码实现

class Solution {

    /**
     * 迭代 - 借助队列完成每一层树的遍历
     * 依次出队并统计数目
     */
    public int countNodes(TreeNode root) {
        // 检查输入
        if (root == null) {
            return 0;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        // 统计节点个数
        int cnt = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 累加当前层节点总数
            cnt += size;
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.poll();
                // 左子树不为空
                if (peek.left != null) {
                    queue.offer(peek.left);
                }
                // 右子树不为空
                if (peek.right != null) {
                    queue.offer(peek.right);
                }
            }
        }
        return cnt;
    }
}

执行结果

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

题目 03 - 平衡二叉树

原题链接

LeetCode 110 - 平衡二叉树

题目描述

给定一个二叉树,判断它是否是 平衡二叉树

思路

递归

  • 定义一个辅助函数 getHeight 返回类型: int ;
  • 如果不为平衡二叉树,返回 -1; 否则返回一个整数

递归终止条件:

  • 当前节点为 null; 返回 0

单层递归条件:

  • 如果左右子树高度返回 -1;说明已经不是平衡;直接返回 -1;
  • 如果左右子树高度 leftHeight 和 rightHeight 的高度差大于 1 直接返回 -1
  • 否则返回左右子树中的最大高度 + 1 该层父节点

代码实现

class Solution {

    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (root.left == null && root.right == null) {
            return true;
        }
        return getHeight(root) == -1 ? false : true;
    }

    public int getHeight(TreeNode node) {
        if (node == null) {
            return 0;
        }
        // 左子树高度
        int leftHeight = getHeight(node.left);
        // 右子树高度
        int rightHeight = getHeight(node.right);
        if (leftHeight == -1 || rightHeight == -1) {
            return -1;
        }
        // 高度差大于 1
        if (Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        }
        return 1 + Math.max(leftHeight, rightHeight);
    }
}

执行结果

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

题目 04 - 二叉树的所有路径

原题链接

LeetCode 257 - 二叉树的所有路径

题目描述

给你一个二叉树的根节点root,按任意顺序,返回所有从根节点到叶子节点的路径。 叶子节点是指没有子节点的节点。

思路 01 - 递归(回溯)

深度优先(回溯)

  • 一个 List 用于收集所有路径
  • 借助可变字符串记录路径 (String 或者 StringBuilder) 都可以

递归终止条件:

  • 当前节点 node 为叶子节点,即 node.left == null && node.right == null
  • 将节点值 val 记录后收集

单层递归条件:

  • 记录当前节点 val
  • 如果 node.left 不为空,记录路径字符串先加上 -> 后记录该节点
  • 如果 node.right 不为空,记录路径字符串先加上 -> 后记录该节点

回溯撤销

  • 设当前层可变字符串长度为 currLen; 由于每层递归添加 ->val
  • 因此需要撤销 currLen 往后的所有记录;

图示

回溯-求二叉树内的所有路径.jpg

代码实现 - StringBuilder

class Solution {

    // 收集路径
    private List<String> ans = new ArrayList<>();
    // 记录路径
    private StringBuilder sb = new StringBuilder();

    public List<String> binaryTreePaths(TreeNode root) {
        // 检查输入
        if (root == null) {
            return ans;
        }
        StringBuilder sb = new StringBuilder();
        dfs(root, sb);
        return ans;
    }

    // 深度优先遍历
    public void dfs(TreeNode node, StringBuilder sb) {
        if (node.left == null && node.right == null) {
            // 递归结束: 收集路径
            sb.append(node.val);
            ans.add(new String(sb));
            return;
        }
        sb.append(node.val);    // 当前节点不为空
        int currLen = sb.length();  //记录当前长度,用于撤销回溯
        if (node.left != null) {
            dfs(node.left, sb.append("->"));  //节点之间用 -> 连接
            sb.delete(currLen, sb.length());  //撤销 -> 和记录的下层节点
        }
        if (node.right != null) {
            dfs(node.right, sb.append("->"));
            sb.delete(currLen, sb.length());
        }
    }
}

代码实现 - String

class Solution {

    // 收集所有路径
    private List<String> ans = new ArrayList<>();
    // 记录路径
    private String path = "";

    public List<String> binaryTreePaths(TreeNode root) {
        // 检查输入
        if (root == null) {
            return ans;
        }
        backTracking(root, path);
        return ans;
    }

    public void backTracking(TreeNode node, String path) {
        // 递归终止条件
        if (node.left == null && node.right == null) {
            path += node.val; // 加入该节点值
            ans.add(new String(path)); // 收集
            return;
        }
        path += node.val;
        int curLen = path.length(); // 记录当前的长度
        if (node.left != null) {
            backTracking(node.left, path + "->"); // 进入下一层,节点之间使用 -> 连接
            path = path.substring(0, curLen); // 撤销
        }
        if (node.right != null) {
            backTracking(node.right, path + "->");
            path = path.substring(0, curLen); // 撤销
        }
    }
}

执行结果

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

思路 02 - 迭代

  • 队列层序所有二叉树节点
  • 栈维护元素: 树节点 + 路径; 每次出栈,一个树节点和其所在路径为一组
  • 入栈顺序: 先结点 node 后路径 path;
  • 出栈顺序: 先路径 path 后节点 node
  • 收集路径条件: 为叶子节点,收集该路径 list.add(path)

代码实现

class Solution {

    // 结果
    private List<String> ans = new ArrayList<>();

    public List<String> binaryTreePaths(TreeNode root) {
        if (null == root) {
            return ans;
        }

        // 栈同时存储节点和路径
        Stack<Object> stack = new Stack<>();

        // 根节点和初始路径入栈
        stack.push(root);
        stack.push(root.val + "");

        // 当栈不为空,出栈
        while (!stack.isEmpty()) {
            // 路径后入先出,节点信息先入后出
            String path = (String) stack.pop();
            TreeNode node = (TreeNode) stack.pop();

            // 终止条件, node 为叶子节点
            if (node.left == null && node.right == null) {
                ans.add(path);
            }

            // 左子节点不为空,添加新的路径记录
            if (node.left != null) {
                stack.push(node.left);
                stack.push(path + "->" + node.left.val);
            }

            // 右子节点不为空; 添加新的路径记录
            if (node.right != null) {
                stack.push(node.right);
                stack.push(path + "->" + node.right.val);
            }
        }
        return ans; // 返回收集的所有结果
    }
}

执行结果

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

题目 05 - 求二叉树左叶子节点之和

原题链接

LeetCode 404 - 左叶子之和

题目描述

给定二叉树的根节点 root ,返回所有左叶子之和。

思路 01 - 递归

  • 判断是否是左叶子节点的条件
  • 不能仅靠 root.leftroot.right 判断。因为右子节点也符合
  • 需要借助其父节点,即 root.left != null 的前提下: root.left.left == null && root.left.right == null
  • 单层递归返回条件:
    • 当前节点为 null
    • 当前节点为叶子节点
  • 遍历顺序: 前序遍历

代码实现

class Solution {
    /*
     遍历顺序: 二叉树前序遍历
     增加判断逻辑: 是否为左叶子之和
    */
    public int sumOfLeftLeaves(TreeNode root) {
        if(root == null) {  //[中]递归返回条件 01: 节点为空
            return 0;
        }
        if(root.left == null && root.right == null) {   //[中]递归返回条件 02: 叶子节点
            return 0;
        }
        int leftSum = sumOfLeftLeaves(root.left);   //[左子树] 获取左子树左叶子之和(如果存在)
        //判断是否为: 左叶子节点
        if(root.left != null && (roo.left.left == null && root.left.right == null)) {
            //更新左子节点总和
            leftSum = root.left.val;    
        }
        int righSum = sumOfLeftLeaves(root.right);  //[右子树] 获取右子树左叶子之和(如果存在)
        return leftSum + righSum;
    }
}

执行结果

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

思路02 - 迭代

  • 借助队列遍历所有节点
  • 每次出队判断其左子节点是否为左叶子
  • 判断条件: node.left != null 的前提下: node.left.left == null && node.left.right == null

代码实现

class Solution {

    public int sumOfLeftLeaves(TreeNode root) {
        // 检查输入
        if (root == null) {
            return 0;
        }
        // 队列,辅助遍历
        Deque<TreeNode> queue = new LinkedList<>();
        // 根节点入队
        queue.offer(root);
        // 记录左节点总和
        int sum = 0;
        while (!queue.isEmpty()) {
            int size = queue.size(); // 当前层树节点总数
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                // 是否为左叶子节点
                if (node.left != null && node.left.left == null && node.left.right == null) {
                    // 累加左子节点
                    sum += node.left.val;
                }
                // 左节点入队
                if (node.left != null) {
                    queue.offer(node.left);
                }
                // 右节点入队
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        return sum;
    }
    
}

执行结果

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