文章:代码随想录
平衡二叉树:
平衡二叉树是指这个二叉树左右子树的最大深度差不超过1.
思路:
那么这道题得用后序遍历,因为要先收集左右子树的深度,然后最后在中间节点做数据对比来返回.
接下来有后序递归法和迭代法:
//后序遍历
public boolean isBalanced(TreeNode root) {
return getHeight(root) != -1;
}
private int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
//如果有任意一边/一个节点为-1,那证明整个树已经不是平衡二叉树了,所以也可以直接返回-1.
if (leftHeight == -1) {
return -1;
}
int rightHeight = getHeight(root.right);
if (rightHeight == -1) {
return -1;
}
// 左右子树高度差大于1,return -1表示已经不是平衡树了
if (Math.abs(leftHeight - rightHeight) > 1) {
return -1;
}
//若不大于1,则是平衡树
//每次返回给上一层也要带上当前中节点的深度,所以要+1;
return Math.max(leftHeight, rightHeight) + 1;
}
//迭代法
//有两种思路来求每个节点的高度,第一种是迭代法,第二种是用node.val来存当前节点的最高高度。
//用层序遍历来求高度,整个方法时间复杂度为O(n^2),效率较低,计算高度时会重复遍历。
//第二种方法求高度呢算是第一种的优化,利用TreeNode.val来保存当前结点的高度,这样就不会有重复遍历。
// 获取高度算法时间复杂度可以降到O(1),总的时间复杂度降为O(n)。
//这里外层用stack实现遍历树很巧妙,是用一个指针从根节点,一路沿着root.left到树底部,将这些左侧最顶部的节点加入栈.
//然后栈将每一个节点从头部弹出,从这个节点不断.right,将这个节点的右边一条部分全部加入栈,到右侧底部之后再一一弹出返回.(像斜下来的右斜线)
//在触右侧底部返回弹出元素的过程中呢,再不断比较这些节点的左右子树的高度,看他们的绝对值大不大于一.
//这个回弹过程的临界条件有两个:1.inNode.right == null 证明触底了 2. inNode.right == pre证明右边的节点是已经遍历过的.
public boolean isBalancedStack(TreeNode root) {
if (root == null) {
return true;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode pre = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
TreeNode inNode = stack.peek();
// 右结点为null或已经遍历过
if (inNode.right == null || inNode.right == pre) {
// 输出
if (Math.abs(getHeight(inNode.left) - getHeight(inNode.right)) > 1) {
return false;
}
stack.pop();
pre = inNode;
root = null;// 当前结点/链条下,没有要遍历的结点了
} else {
root = inNode.right;// 右结点还没遍历,遍历右结点
}
}
return true;
}
/**
* 层序遍历,求结点的高度
*/
public int getHeightLevel(TreeNode root) {
if (root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
int depth = 0;
while (!deque.isEmpty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode poll = deque.poll();
if (poll.left != null) {
deque.offer(poll.left);
}
if (poll.right != null) {
deque.offer(poll.right);
}
}
}
return depth;
}
/**
* 优化后的方法,求结点的高度
*/
public int getHeightVal(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = root.left != null ? root.left.val : 0;
int rightHeight = root.right != null ? root.right.val : 0;
int height = Math.max(leftHeight, rightHeight) + 1;
root.val = height;// 用TreeNode.val来保存当前结点的高度
return height;
}
二叉树的所有路径:
这里主要是要理解回溯的过程,就是每次找到到达根节点的路径之后,要往回返回的同时,记录的节点要往回弹出.
代码:
public List<String> binaryTreePaths(TreeNode root) {
List<String> result=new ArrayList<>();
//这里字符串拼接涉及"->",所以直接用stringBuffer拼接后序处理会有点麻烦,直接用List记录最后再拼接会更方便
List<Integer> path = new ArrayList<>();
getPath(root,path,result);
return result;
}
//前序遍历,因为是求从根节点开始的路径,从上往下计数,像深度.
private void getPath(TreeNode node, List<Integer> path, List<String> result) {
path.add(node.val);
if(node.left==null && node.right==null){
//字符串拼接
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i <path.size() ; i++) {
stringBuffer.append(path.get(i));
stringBuffer.append("->");
}
//删除最后一个箭头
stringBuffer.delete(stringBuffer.length()-2,stringBuffer.length());
result.add(stringBuffer.toString());
return;
}
if(node.left!=null) {
getPath(node.left,path,result);
//回溯过程,把下一层加入的节点的值弹出,然后再让这个stringBuffer/List加入右子树遍历.(StringBuffer/Lis就变成遍历到当前节点的状态)
//整个回溯过程就是把从叶子节点的最后一个节点逐层往上一个一个弹出.
path.remove(path.size()-1);
}
if(node.right!=null) {
getPath(node.right,path,result);
//回溯过程,也是一样的逻辑,把下一层/叶子节点的值弹出,让stringBuffer/Lis变成遍历到当前节点的状态.
path.remove(path.size()-1);
}
}
左叶子节点之和
这一题对于递归,其实只能用后序,为什么?因为需要最后将左右子树的左叶子节点的值返回做累加。
对于迭代就可以前中后序,为什么呢?因为迭代用栈记录,只需要全部遍历一边然后找到符合要求的节点,然后把值加起来就可以了。那可能会疑问,那递归为什么不可以?
下面的后序遍历第一种其实就告诉你答案,因为如果中节点先遍历,那么如果是目标节点就直接返回,这时候右子树就无法进入,如果右子树也存在叶子节点,就会导致少结果.那你会问左子树呢?左子树其实是遍历过了,因为是遍历到叶子节点的上一层。在判断这个中节点是不是目标节点的时候会判断这个节点的左节点的左右节点是否为空,那么这个时候实际上就已经把左节点/左子树便利了。更详细地看代码注解.
代码:
//后序遍历
int sum=0;
public int sumOfLeftLeavesPost(TreeNode root) {
getSumPost(root);
return sum;
}
//如果按往常的遍历遍历到最后一层,那么只能知道当前节点是否为叶子节点,但是无法判断这个叶子节点是不是父节点的左孩子,所以遍历到叶子节点的上一层来判断。
private void getSumPost(TreeNode root) {
//左右中,这个逻辑不能用前中序,因为这个遍历是遍历到叶子节点的倒数第二层,很有可能在处理中节点的时候,直接就找到了左叶子节点,那么它就会直接return.
//试想如果是前序,那么左右子树的getSum就进不去,当然这个节点的左子树已经算遍历过了,因为中节点处理结果的时候加上了左叶子节点的值,但是这个节点的右子树就完全遍历不到。
if(root.left!=null){getSumPost(root.left);}
if(root.right!=null){getSumPost(root.right);}
if(root.left!=null&&root.left.left==null&& root.left.right==null){ sum+= root.left.val; return;}
}
//后序遍历第二种
//思路是找左右子树的左叶子节点之和
public int sumOfLeftLeavesPost2(TreeNode root) {
if(root==null){return 0;}
int leftValue = sumOfLeftLeavesPost2(root.left); // 左
int rightValue = sumOfLeftLeavesPost2(root.right); // 右
int midValue = 0;
if (root.left != null && root.left.left == null && root.left.right == null) {
midValue = root.left.val;
}
int re = midValue + leftValue + rightValue; // 中
return re;
}
//对于迭代法,前中后序都可以,因为从上往下遍历找到节点值相加即可.
//非递归,前序迭代法
public int sumOfLeftLeavesPre(TreeNode root) {
if(root==null){return 0;}
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
int sum=0;
while(!stack.isEmpty()){
TreeNode top=stack.pop();
if(top!=null){
if(top.right!=null) stack.push(top.right);
if(top.left!=null) stack.push(top.left);
stack.push(top);
stack.push(null);
}else{
TreeNode node=stack.pop();
if (node.left != null && node.left.left == null && node.left.right == null) {sum+= node.left.val;}
}
}
return sum;
}