简介
题目 01: 最小二叉树深度 (LeetCode 111)
题目 02: 完全二叉树的节点个数(LeetCode 222)
题目 03: 判断是否是平衡二叉树(LeetCode 110)
题目 04: 求二叉树的所有路径(LeetCode 257)
题目 05: 求二叉树左叶子节点之和(LeetCode 404)
题目 01 - 求二叉树的最小深度
原题链接
题目描述
给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
思路
- 借助队列实现二叉树层序遍历
- 层序遍历,当遍历到左右节点都为空的时候;即找到了当前树的叶子节点,得到最小深度
代码实现
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 - 完全二叉树的节点个数
原题链接
题目描述
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
若最底层为第 h 层,则该层包含 1~ 2h 个节点。
思路 - 递归
递归
- 递归终止条件: 当前节点为空,返回 0
- 每层逻辑: 获取左子树节点个数
leftNum和右子树rightNum; 返回当前层根节点+左右子树总数leftNum + rightNum + 1 - 叶子节点
leftNum和rightNum皆为 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 - 平衡二叉树
原题链接
题目描述
给定一个二叉树,判断它是否是
平衡二叉树
思路
递归
- 定义一个辅助函数
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 - 二叉树的所有路径
原题链接
题目描述
给你一个二叉树的根节点
root,按任意顺序,返回所有从根节点到叶子节点的路径。 叶子节点是指没有子节点的节点。
思路 01 - 递归(回溯)
深度优先(回溯)
- 一个 List 用于收集所有路径
- 借助可变字符串记录路径 (String 或者 StringBuilder) 都可以
递归终止条件:
- 当前节点
node为叶子节点,即node.left == null && node.right == null - 将节点值
val记录后收集
单层递归条件:
- 记录当前节点
val - 如果
node.left不为空,记录路径字符串先加上->后记录该节点 - 如果
node.right不为空,记录路径字符串先加上->后记录该节点
回溯撤销
- 设当前层可变字符串长度为
currLen; 由于每层递归添加->val - 因此需要撤销
currLen往后的所有记录;
图示
代码实现 - 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 - 求二叉树左叶子节点之和
原题链接
题目描述
给定二叉树的根节点 root ,返回所有左叶子之和。
思路 01 - 递归
- 判断是否是左叶子节点的条件
- 不能仅靠
root.left和root.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)