剑指Offer_#55 - II_平衡二叉树(LeetCode#110)
Contents
题目
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/
9 20
/
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/
2 2
/
3 3
/
4 4
返回 false 。
限制:
1 <= 树的结点个数 <= 10000
思路分析
这一题是有两种方法,一种是普通的递归(自顶向下),另一种是分治法(提前阻断,自底向上)。说实话之前对于递归,分治分的不是很清楚,我感觉所谓的分治法写出来不还是递归吗?到底区别在哪里呢?通过对比这道题的两种解法,就能理解清楚了。
递归 vs 分治
联系: 分治算法往往使用递归的代码实现的。
区别: 但是分治和递归不是一个概念,分治是一种算法思想,递归是一种实现方法(即自己调用自己)。分治算法的代码往往具有提前阻断的特性。
解答
方法1:普通递归(自顶向下)
算法流程
类似前序遍历
- 先处理当前节点,即判断当前节点的左右子树高度差距是否不超过1。
- 这里又涉及到深度的计算,需要写一个辅助函数
depth()
- 这里又涉及到深度的计算,需要写一个辅助函数
- 然后处理左右子节点,分别判断左右子节点是否满足上述条件。
这个写法的问题在于depth()
方法计算高度的时候会重复访问同一个节点。因为这是自顶向下的写法,所以无法利用子树的高度计算当前节点的高度。
解答1:普通递归(自顶向下)
class Solution {
//前序遍历递归:先判断当前节点是否平衡,然后判断左右子树是否平衡
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
//辅助函数:计算每个子树的最大深度,也是一个递归函数
private int depth(TreeNode root){
if(root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}
}
复杂度分析
时间复杂度:O(nlogn)
,因为每次调用depth
会带来O(logn)
的复杂度
空间复杂度:O(n)
方法2:分治法(提前阻断,自底向上)
判断一棵树是否是平衡树,也是一个递归问题,因为需要递归地判断每个节点的左右子树深度是否相差不超过1,即需要判断每个节点是否是平衡的。
原问题与子问题结构相似,符合分治思想的特征,所以用分治法解决。
分治法模板
// 伪代码
public class Solution {
public ResultType traversal(TreeNode root) {
if (root == null) {
// do something and return
}
// Divide
ResultType left = traversal(root.left);
ResultType right = traversal(root.right);
// Conquer
ResultType result = Merge from left and right
return result;
}
}
一般来说,
分治法代码会把递归调用的结果(也就是小问题的答案)保存到局部变量中,这个步骤叫做分(Divide)。
这里就可以解释什么叫做提前阻断,因为这里的递归调用
ResultType left = traversal(root.left);
ResultType right = traversal(root.right);
会把代码阻塞在这个位置,直到满足终止条件,即遇到null
,才会继续运行下面的代码。
接下来将利用保存下来的局部变量去解决更大的问题(上一层递归函数),这个步骤叫做治(Conquer)。
第一次进行治的过程是在最后一级递归调用,然后逐级回溯到第一级递归调用,这个过程是自底向上的。
特殊地,在本题当中的原问题和小问题又是什么?
- 原问题:整棵树是否是平衡二叉树?
- 小问题:根节点的左右子树是否是平衡二叉树?
根据这个思路就可以写出代码了。写出的代码依然是递归形式。
算法设计
根据问题的特点,我们采用后续遍历(更准确的说是分治法),先访问左右子树,再访问根节点,这样可以一边访问树的节点,一边判断当前的节点是否是平衡的。
递归函数isBalancedHelper
返回值的含义:
- -1表示此节点不平衡,返回false
- 不是-1,则代表当前节点的深度,从叶节点开始,这个深度的返回值可以用于递推其父节点的深度,避免第二次遍历同一个节点。
解答2:分治法(提前阻断,自底向上)
class Solution {
public boolean isBalanced(TreeNode root) {
return isBalancedHelper(root) != -1;
}
private int isBalancedHelper(TreeNode root){
if(root == null) return 0;
int left = isBalancedHelper(root.left);
//如果left/right是-1,就不需要计算diff了,因为两个都是-1的时候,diff是0,可能就误判了
if(left == -1) return -1;
int right = isBalancedHelper(root.right);
if(right == -1) return -1;
int diff = Math.abs(left - right);
return diff <= 1 ? Math.max(left,right) + 1:-1;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)