二叉树习题的思想:
写递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要跳入递归的细节!!
如果你跳入了递归的细节,你就大错特错了!
第一步 :「确定使用是从顶到底处理还是从底到顶:」我们首先看看这个这道题是从底往上还是从顶往下进行处理。
第二步: 「确定递归函数的参数和返回值:」
如果是从底往上呢,则使用后序遍历,在这里的话,一般有一个返回值用于处理对于当前节点的处理。
对于从顶往底呢,则使用前序遍历,那么情况则不一定,有可能可以返回为空,也可能有返回值。
第三步: 「确定单层递归的逻辑:」确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
第四步:「 确定递归出口」。
tip:当你能够真正的理解二叉树就是利用前中后序层次遍历框架,去解决问题~你就算入门了。
然后这里给出两份不错的资料
一个是 程序员Carl 公众号的文章:
一个则是labuladong的算法小抄
1.有关前序遍历框架的习题
/*
112 路径总和
2020、12/5
* 二刷:没有做出来
* 本题的思路:
本题我们应该使用的从上往下的顺序,先去比较当前节点,首先我们看看当前节点是不是叶子节点,
如果是的话,看看是否sum==val,
如果不是呢,首先我们sum-val ,然后再跟我们的左右孩子进行比较。
返回参数应该是boolean
* */
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum == root.val;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
/*
* 2020/11/9
* 617 合并二叉树
*
* 这道题很明显使用的就是前序遍历框架
* 但是有一点,比较特殊就是他的递归递归出口有点特别,可以瞅瞅
* */
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null) return t2;
if(t2==null) return t1;
TreeNode merged = new TreeNode(t1.val + t2.val);
merged.left = mergeTrees(t1.left,t2.left);
merged.right= mergeTrees(t1.right,t2.right);
return merged;
}
/*
* 2020/11/10
* 572. 另一个树的子树
* 思路:首先写一个check函数这个函数的作用就是看看这个s当前节点是否和t的当前节点是否相同,
* 然后继续比较s和t节点左右节点。总体来说就是对单个s的节点而言
* 这道题是一种自顶而下的思想,和我们之前解决的平衡树的判定很像,都是首先判定当前节点,再去判定其他节点
* */
public boolean isSubtree(TreeNode s, TreeNode t) {
return dfs(s,t);
}
public boolean dfs(TreeNode s, TreeNode t) {
if (s == null) return false;
return check(s, t) || dfs(s.left, t) || dfs(s.right, t);
}
public boolean check(TreeNode s, TreeNode t) {
if (s == null && t == null) return true;
if (s == null || t == null || s.val != t.val) return false;
return check(s.left, t.left) && check(s.right, t.right);
}
/*
* 110. 平衡二叉树
* 2020/11/9
方法一:前序遍历框架,自顶向下
* 思路:具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,
* 首先计算左右子树的高度,如果左右子树的高度差是否不超过 1,
* 再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。
* 这是一个自顶向下的递归的过程。
* */
/
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
else
return Math.abs(height(root.left) - height(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int height(TreeNode root) {
if (root == null) return 0;
return Math.max(height(root.left), height((root.right))) + 1;
}
/*
本题一看就是使用前序遍历的框架,首先我们看看根节点是否为空,不为空则看看她左右孩子空不空
然后我们再去把左孩子的右孩子和右孩子的左孩子,
以及左孩子的左孩子和右孩子的右孩子,扔到框架内去判断。
*
* */
public boolean isMirror01(TreeNode left,TreeNode right){
if (left==null&&right==null) return true;
else if (left!=null&&right==null) return false;
else if (left==null&&right!=null) return false;
else if (left.val!=right.val) return false;
boolean l = isMirror01(left.right,right.left);
boolean r = isMirror01(left.left,right.right);
return l&&r;
}
*
路径总和
2020/12/6
* 二刷一次aC 使用 前序遍历。再用主函数 遍历每个节点
*
* */
public class day13_pathSum_M {
int flag = 0;
public int pathSum(TreeNode root, int sum) {
if(root == null)
return 0;
sum(root, sum);
pathSum(root.left, sum);
pathSum(root.right, sum);
return flag;
}
public void sum(TreeNode root, int sum) {
if (root != null) {
sum -= root.val;
if(sum == 0)
flag++;
sum(root.left, sum);
sum(root.right, sum);
}
}
}
/*
* 二刷失败。
* 首先我们知道对于每个节点要做的事情就是看看我是不是在最后一层,然后我是不是最左边的元素。
* 如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行
* 所以要找深度最大的叶子节点。
那么如果找最左边的呢?可以使用前序遍历,这样才先优先左边搜索,
* 然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
*
* 如果需要遍历整颗树,递归函数就不能有返回值。如果需要遍历某一条固定路线,递归函数就一定要有返回值
* */
int maxLength;
public void dfs(TreeNode root, int leftLength) {
if (root.left == null && root.right == null) {
if (leftLength > maxLength) {
maxLength = leftLength;
}
return;
}
if (root.left != null) {
dfs(root.left, leftLength + 1);
}
if (root.right != null) {
dfs(root.right, leftLength + 1);
}
return;
}
int findBottomLeftValue01(TreeNode root) {
dfs(root, 0);
return maxLength;
}
2.有关后序遍历框架的习题
/*
* 543 二叉树的直径
* 本题的思路就是从底往上,分别对每个节点找寻他左子树的
* 最大高度以及右子树的最大高度然后加上父节点 然后跟最大值进行比较,
* 由此我们得出他是先求左右孩子再求当前节点,所以使用的是后序遍历的框架。
* 对于后序遍历我们发现一定会返回东西、
*
* */
public int depth(TreeNode root) {
if (root == null) return 0;
int L = depth(root.left);
int R = depth(root.right);
if (L + R > this.max) this.max = L + R;
new ArrayList<>();
return Math.max(L, R) + 1;
}
*226 翻转二叉树
* 这道题很明显,就是从底层往往上去翻转,所以一定是使用后序遍历
* 然后我们对于当前节点的操作就是
* root.left = right;
root.right = left;
* 然后返回值是TreeNode
*
* */
public class day12_invertTree_E {
public TreeNode invertTree(TreeNode root) {
if(root==null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
/*
* 110. 平衡二叉树
* 2020/11/9
方法一:前序遍历框架,自顶向下
* 思路:具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,
* 首先计算左右子树的高度,如果左右子树的高度差是否不超过 1,
* 再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。
* 这是一个自顶向下的递归的过程。
* */
/
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
else
return Math.abs(height(root.left) - height(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int height(TreeNode root) {
if (root == null) return 0;
return Math.max(height(root.left), height((root.right))) + 1;
}
/*
* 思路二:自底向上,后序遍历框架
* 自底向上递归的做法类似于后序遍历,对于当前遍历到的节点,
* 先递归地判断其左右子树是否平衡,再判断以当前节点为根的子树是否平衡。
* 如果一棵子树是平衡的,则返回其高度(高度一定是非负整数),否则返回 -1−1。
* 如果存在一棵子树不平衡,则整个二叉树一定不平衡。
* */
public boolean isBalanced01(TreeNode root) {
return height(root) >= 0;
}
public int height01(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = height(root.left);
int rightHeight = height(root.right);
if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
return -1;
} else {
return Math.max(leftHeight, rightHeight) + 1;
}
}
/*
687. 最长同值路径
* 二刷错误不会写呀!
* 从低往上去反找寻 。使用后序遍历的思想。从最底层 反推回来。
* 本题的话,使用的是后序遍历的思想,首先对于找到左右孩子的最长同值路径,然后跟我本身对比一下,
* 如果一样呢,则加上自己。
* */
public int arrowLength(TreeNode node) {
if (node==null) return 0;
/*
* 使用后序遍历,从底层反退回来
* */
int left = arrowLength(node.left);
int right = arrowLength(node.right);
int arrowLeft = 0,arrowRight = 0;
/*
*处理当前节点
* */
if (node.left!=null&&node.val==node.left.val){
arrowLeft =1+left;
}
if (node.right!=null&&node.val==node.right.val){
arrowRight =1+right;
}
return Math.max(max,arrowLeft+arrowRight);
}
/*
* 左叶子之和
* 方法三:使用三步走
*
* */
int sumOfLeftLeaves02(TreeNode root) {
if (root == null) return 0;
/*
* 本题使用后序遍历
* 为啥呢,因为我们首先 找到左右孩子的左子节点的和然后加上我自己
* 那不就是成了吗?
* */
int leftValue = sumOfLeftLeaves02(root.left); // 左
int rightValue = sumOfLeftLeaves02(root.right); // 右
// 中
int midValue = 0;
/*
这一步就可以判断该节点是左孩子了
* */
if (root.left!=null && root.left.left==null && root.left.right==null) { // 中
midValue = root.left.val;
}
int sum = midValue + leftValue + rightValue;
return sum;
}
3.层次遍历框架
/*
二叉树的层平均值
* 二刷使用的是层次遍历的方法。
* 2020/12/7
*
*
* */
public List<Double> averageOfLevels02(TreeNode root) {
Deque<TreeNode> que = new LinkedList<>();
List<Double> list = new ArrayList<>();
que.offer(root);
while (!que.isEmpty()){
double sum = 0;
int size = que.size();
for (int i = 0;i<size;i++){
TreeNode t =que.poll();
sum+=t.val;
if (t.left!=null){
que.add(t.left);
}
if (t.right!=null){
que.add(t.right);
}
}
list.add(sum/size);
}
return list ;
}