平衡二叉树
终止条件:子树为空的时候,空树自然是平衡二叉树了。
返回值:而对于一颗树,它是一个平衡二叉树需要满足三个条件:它的左子树是平衡二叉树,它的右子树是平衡二叉树,它的左右子树的高度差不大于1。
本级递归应该做什么:一级递归有三个节点,root、left、right,需要判断left和right是不是平衡的且以left为根节点和以right为根节点的树高度差是否<=1,如果是,则返回true。
public boolean isBalanced(TreeNode root) {
if(root==null)return true;
//一层递归做的事情
if(isBalanced(root.left) && isBalanced(root.right) && Math.abs(dfs(root.left)-dfs(root.right))<=1)return true;
else return false;
}
public int dfs(TreeNode root) {//求树的高度
if(root==null) {
return 0;
}
//返回值是左子树和右子树最大高度值+根节点应该占得高度(1)
return Math.max(dfs(root.left), dfs(root.right))+1;
}
二叉树的所有路径
很简单的一道题,但是很多细节
可以利用new每次传参都开一个new StringBuilder(),维护每个节点的路径。
public List<String> binaryTreePaths(TreeNode root) {
List<String> res=new LinkedList<>();
if(root==null)return res;
backtrack(root, res, new StringBuilder());
return res;
}
public void backtrack(TreeNode root, List<String> res, StringBuilder result) {
if(root==null)return;
//只要这个结点不是null就加入路径中去
result.append(root.val);
if(root.left==null && root.right==null) {
res.add(result.toString());
return;
}
if(root.left!=null) {
//为什么要new而不用恢复现场去回溯?
//因为每次加入一个新节点值时,需要连接一个"->",如果remove的话只能去除一个值,而"->"是两个字符
//所以每次new一下来维护一个根节点的路径
backtrack(root.left, res, new StringBuilder(result).append("->"));
}
if(root.right!=null) {
backtrack(root.right, res, new StringBuilder(result).append("->"));
}
}
左叶子和
判断左叶子的条件:为叶子结点&&是左结点
flag来标记是否为左结点,根节点的左、右叶子都是null来判断是否为叶子结点
int sum=0;
public int sumOfLeftLeaves(TreeNode root) {
if(root==null)return 0;
backtrack(root, false);
return sum;
}
public void backtrack(TreeNode root, boolean flag) {
if(root==null)return;
if(root.left==null && root.right==null && flag) {
sum+=root.val;
return;
}
backtrack(root.left, true);
backtrack(root.right, false);
}
只关注终止条件、参数、返回值:
参数:当前结点的左、右节点
返回值:左、右子树的左叶子结点和+当前本结点的左叶子结点,若有就是if里面赋值的sum,若没有就是0;
public int sumOfLeftLeaves(TreeNode root) {
if(root==null)return 0;
int sum=0;
if(root.left!=null && root.left.left==null && root.left.right==null) {
sum=root.left.val;
}
return sumOfLeftLeaves(root.left)+sumOfLeftLeaves(root.right)+sum;
}
路径总和
注意:
这个什么时候+sum?
dfs(root.left, targetSum, sum+root.val);
//不能这样,因为你传入的参数是此根节点的左节点了,可是sum和还没加到左节点的值
//所以这个+的操作应该放在判断此结点是否为null后面,如果不为null,就应该加上这个结点
前两版本的代码差别在何时+sum上,都能ac
boolean flag=false;
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
dfs(root, targetSum, 0);
return flag;
}
public void dfs(TreeNode root, int targetSum, int sum) {
if(root==null) return;
sum+=root.val;
if(root.left==null && root.right==null) {
if(sum==targetSum)flag=true;
}
dfs(root.left, targetSum, sum);
dfs(root.right, targetSum, sum);
}
boolean flag=false;
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
dfs(root, targetSum, root.val);
return flag;
}
public void dfs(TreeNode root, int targetSum, int sum) {
if(root==null) return;
if(root.left==null && root.right==null) {
if(sum==targetSum)flag=true;
}
if(root.left!=null)
dfs(root.left, targetSum, sum+root.left.val);
if(root.right!=null)
dfs(root.right, targetSum, sum+root.right.val);
}
更简洁的版本:
传入参数:每层根节点和此时根节点所需要的sum值
终止条件:结点为空
返回值:此结点的左子树-该节点的值是否和为target或者此结点的右子树-该节点的值是否和为targe
构建二叉树(中序+后序)
思想:
看一下中序和后序有什么特点,中序[9,3,15,20,7]
,后序[9,15,7,20,3]
;
有如下特征:
- 后序中右起第一位
3
肯定是根结点,我们可以据此找到中序中根结点的位置rootin
; - 中序中根结点左边就是左子树结点,右边就是右子树结点,即
[左子树结点,根结点,右子树结点]
,我们就可以得出左子树结点个数; - 后序中结点分布应该是:
[左子树结点,右子树结点,根结点]
; - 根据前一步确定的左子树个数,可以确定后序中左子树结点和右子树结点的范围;
- 如果我们要前序遍历生成二叉树的话,下一层递归应该是:
- 左子树:
root->left = pre_order(中序左子树范围,后序左子树范围,中序序列,后序序列);
; - 右子树:
root->right = pre_order(中序右子树范围,后序右子树范围,中序序列,后序序列);
。
- 左子树:
- 每一层递归都要返回当前根结点
root
注意:一定要先构建左子树再构建右子树
每一层要做的事情就是找出根节点,再找出左子树、右子树的数组范围,最后分别构建左右子树。
public TreeNode buildTree(int[] inorder, int[] postorder) {
return build(0, inorder.length-1, 0, postorder.length-1, inorder, postorder);
}
public TreeNode build(int inleft, int inright, int postleft, int postright, int[] inorder, int[] postorder) {
if(inleft>inright)return null;
TreeNode root=new TreeNode(postorder[postright]);
int point=0;
//记录根节点
for(int i=0; i<=inright; i++) {
if(postorder[postright]==inorder[i]) {
point=i;
break;
}
}
//递归求解左子树和右子树
root.left=build(inleft, point-1, postleft, postleft+point-1-inleft, inorder, postorder);
root.right=build(point+1, inright, postleft+point-inleft, postright-1, inorder,postorder);
return root;
}
构建二叉树(前序+中序)
public TreeNode buildTree(int[] preorder, int[] inorder) {
return build(0, preorder.length-1, 0, inorder.length-1, preorder, inorder);
}
public TreeNode build(int preleft, int preright, int inleft, int inright, int[] preorder, int[] inorder) {
if(inleft>inright)return null;
TreeNode root=new TreeNode(preorder[preleft]);
int point=0;
for(int i=inleft; i<=inright; i++) {
if(preorder[preleft]==inorder[i]) {
point=i;
break;
}
}
//中左右 左中右
root.left=build(preleft+1, preleft+point-inleft, inleft, point-1, preorder, inorder);
root.right=build(preleft+point-inleft+1, preright, point+1, inright, preorder, inorder);
return root;
}
为什么构建二叉树一定要有中序?因为前序、后序只能只能找出根节点,而不能判断根节点的左子树、右子树是哪些数组,而中序恰好可以解决这个问题,在中序中找到根节点后,可以找出左右子树,相当于确定了树的形状。