递归 + 二叉树

157 阅读10分钟

1. 前言

我们都知道,一般对于二叉树的题型,大部分可以用递归来进行解决?那么,为什么用递归可以解决呢?这是,由二叉树的结构所决定的;二叉树是由一个根元素和两颗不相交的二叉树组成;二叉树的两颗子树分别称为它的左子树和右子树。重点:由于,二叉树本身就是递归的定义,所以,一般二叉树问题都可以使用递归来解决;

2. 递归相关模板

    public void recur () {
        // 1.结束递归
        // 2.处理当前层
        // 3.递归下一层
        // 4.清理当前层
    }

我自己一般写的时候,都先把相应的模板写上,为了提醒自己不忘记相关步骤,其中,第四步清理当前层一般在回溯法时会用到,在二叉树部分一般用不到;

3. 递归注意事项

我自己刚刚开始刷力扣的时候,我对递归就感觉很慌,感觉递归函数很高级,怕一不小心就绕进去了;不太敢用;其实,没必要;一定要确立递归函数就是一个普通函数的思想递归函数的定义就是普通函数的定义;在二叉树相关题目时,先看一下系统给的函数的定义,根据定义判定能不能当递归函数,如果,可以就选取它作为递归函数,不行,就自己写一个递归函数;

4. 刷题

算法的学习其实和数学差不多;正确的理论 + 适量的题目 先选一部分力扣二叉树相关的题来训练-力扣104 111 226 100 101 222 110 112 404 257 113 129 437 235 98 450 108 230 236;以简单题为主,先训练一下思路

力扣104
image.png
第一步:先判定系统给的maxDepth(TreeNode root)能不能当作递归函数;因为,它的定义是返回以root为为根节点的最大深度;但是,没有层级,所以,我们自己重新定义一个递归函数,包含层级会比较好; 第二步: 就是递归四步走了;

class Solution {
    public int maxDepth(TreeNode root) {
        return recur(0, root);
    }
    public int recur(int level, TreeNode root) {
        // 递归四步走
        // 1.结束递归
        if (root == null) {
            return level;
        }
        // 2.当前层处理
        // 3.递归下一层
        return Math.max(recur(level + 1, root.left), recur(level + 1, root.right));
        // 4.清理当前层
    }
}

代码思路就是 如果,root为空,就返回当前的level;如果,不为空,就返回子树中最大的层级;
力扣111

image.png

class Solution {
    /**
     * 1.明确函数的定义,minDepth返回root节点的最小深度
     * 2.因为,没有level来保存当前层级,所以,不具备递归函数的定义
     * 3.我们需要自己定义一个recur(int level, TreeNode root)递归函数
     */
    public int minDepth(TreeNode root) {
        return recur(root, 0);
    }

    public int recur(TreeNode root, int level) {
        // 1.结束递归
        if (root == null) {
            return level;
        }
        
        if (root.left == null && root.right == null) {
            return level + 1;
        }
        // 2.当前层处理
        // 3.递归下一层

        // 左子树为空,右子树非空,叶子节点只会出现在右子树,所以,只要考虑右子树即可
        if (root.left == null && root.right != null) {
            return recur(root.right, level + 1);
        } 
        if (root.right == null && root.left != null) {
            return recur(root.left, level + 1);
        } 

        return Math.min(recur(root.left, level + 1), recur(root.right, level + 1));
        // 4.清理当前层

    }
}

力扣226

image.png

class Solution {
    // invertTree()函数将root节点进行翻转,它自己的定义符合递归定义;可以选为递归函数
    public TreeNode invertTree(TreeNode root) {
        // 1.递归结束
        if (root == null) {
            return null;
        }
        // 2.对当前层处理
        // 3.递归下一层
        TreeNode left = root.left;
        TreeNode right = root.right;
        root.left = invertTree(right);
        root.right = invertTree(left);
        // 4.清理当前层
        return root;
    }
}

像invertTree()函数的定义将root为节点的树进行反转,就符合递归函数的定义了; 上面的思路就是root左子树等于root右子树反转后的结果;root右子树等于root左子树反转后的结果;
力扣100

image.png

class Solution {
    /*
     *isSameTree() 函数代表的是把两个树进行比较,看是不是相同
     *由于不涉及level,所以,isSameTree可以当做递归函数
     */
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        } 
        if (p == null || q == null) {
            return false;
        } 
        if (p.val != q.val) {
            return false;
        } else {
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
        }
    }
}

力扣101

image.png

class Solution {
    // isSymmetric() 判断二叉树是否镜像对称,符合递归定义
    // 但是,镜像参数是需要,左子树和右子树比,参数需要是两个子树
    // 所以,需要重新定义递归函数
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        return recur(root.left, root.right);
    }

    public boolean recur (TreeNode left, TreeNode right) {
        // 1. 结束递归    
        
        if (left == null && right == null) {
            return true;
        }

        if (left == null || right == null) {
            return false;
        }
        if (left.val != right.val) {
            return false;
        }
        // 2. 处理当前层
        // 3. 递归下一层
        // 4. 清理当前层
        return recur(left.left, right.right) && recur(left.right, right.left);
    }
}

力扣110

image.png

class Solution {
    // 平衡二叉树的成立的条件
    // 1. 左子树和右子树的层级相差绝对值为1
    // 2. 左子树是平衡二叉树,右子树也是平衡二叉树
    public boolean isBalanced(TreeNode root) {  
        if (root == null) {
            return true;
        }
        // 1. 结束递归
        int left = high(root.left, 0);
        int right = high(root.right, 0);
        if (Math.abs(left - right) > 1) {
            return false;
        }
        if (root.left == null || root.right == null) {
            return true;
        }
        // 2. 处理当前层
        // 3. 进入下一层
        return isBalanced(root.left) && isBalanced(root.right);
        // 4. 清理下一层
    }

    public int high(TreeNode root, int level) {
        if (root == null) {
            return level;
        }
        return Math.max(high(root.left, level + 1), high(root.right, level + 1));
    }
}

平衡二叉树符合条件:

  1. 左子树和右子树的层级相差绝对值为1

  2. 左子树是平衡二叉树,右子树也是平衡二叉树

本身,这个isBalanced() 具备递归函数定义;然后,只需要使用high()来求出当前节点子树的最大高度即可;
力扣 112

image.png

class Solution {
    // hasPathSum() 返回是否已root为节点,到叶子节点和为target
    // 满足递归函数定义;
    public boolean hasPathSum(TreeNode root, int sum) {

        // 1.结束递归
        // 当传入root为null时;
        if (root == null) {
            return false;
        } 
        // 对叶子节点进行判断       
        if (root != null && root.left == null && root.right == null) {
            if (root.val == sum) {
                return true;
            } else {
                return false;
            }
        } 
        // 2.处理当前层
        // 3.递归下一层
        boolean left = false;
        boolean right = false;
        if (root.left != null) {
            left = hasPathSum(root.left, sum - root.val);
        }
        if (root.right != null) {
            right = hasPathSum(root.right, sum - root.val);
        }
        return (left || right);
        // 4.清理当前层
    }
}

力扣404

image.png
注意哈,这里左叶子的定义,就算右子树被左子树挡住了;那么,右子树的左叶子也是符合条件的;

class Solution {
    // sumOfLeftLeaves() 返回当前节点的左叶子之和;
    // 注意:这里的左叶子定义是 root的左节点存在,但是,该左节点的左右结点都为null;
    public int sumOfLeftLeaves(TreeNode root) {
        // 1.结束递归
        if (root == null) {
            return 0;
        }
        // 2.处理当前层
        int res = 0;
        if (root.left != null && root.left.right == null && root.left.left == null) {
            res = res + root.left.val;
        }
        // 3.递归下一层
        return res + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
        // 4.清理当前层
    }
}

力扣257

image.png

class Solution {
    /**
      * 1.binaryTreePaths(root) 这个函数的话,返回所有从根节点到叶子节点的路径
      * 2. 本身具备递归函数的定义;故选取其作为递归条件 
      */
    public List<String> binaryTreePaths(TreeNode root) {
        // 1. 结束递归
        if (root == null) {
            return new ArrayList<String>();
        }
        // 2. 处理当前层
        // 获取左右子树的路径
        List<String> leftRes = binaryTreePaths(root.left);
        List<String> rightRes = binaryTreePaths(root.right);
        // 将当期root.val加入到左右子树路径上
        int length = leftRes.size() + rightRes.size();
        List<String> res = new ArrayList<>(length);
        if (length == 0) {
            res.add(root.val + "");
            return res;
        }
        
        StringBuilder sBuilder = new StringBuilder();
        

        for (int i = 0; i < leftRes.size(); i++) {
            sBuilder.delete(0, sBuilder.length());
            sBuilder.append(root.val);
            sBuilder.append("->");
            sBuilder.append(leftRes.get(i));
            res.add(sBuilder.toString());
        }

        
        for (int i = 0; i < rightRes.size(); i++) {
            sBuilder.delete(0, sBuilder.length());
            sBuilder.append(root.val);
            sBuilder.append("->");
            sBuilder.append(rightRes.get(i));
            res.add(sBuilder.toString());
        }
        return res;
        // 3. 递归下一层
        // 4. 清理当前层
    }
}

力扣113

image.png

class Solution {
    /**
     * 1. 首先确定递归函数,pathSum() 找出从根节点到叶子节点路径总和等于给定目标和的路径
     * 2. 符合递归函数的定义,选择它作为递归函数
     */
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        // 1. 结束递归
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if (root == null) {
            return res;
        }
        // 达到叶子节点
        if (root != null && root.left == null && root.right == null) {
            List<Integer> cur = new ArrayList<Integer>();
            if (root.val == targetSum) {
                cur.add(root.val);
                res.add(cur);
            }
            return res;
        }
        // 2. 处理当前层
        // 3. 递归下一层
        List<List<Integer>> leftRes = null;
        List<List<Integer>> rightRes = null;
        if (root.left != null) {
            leftRes = pathSum(root.left, targetSum - root.val);
            for (int i = 0; i < leftRes.size(); i++) {
                List<Integer> cur = leftRes.get(i);

                cur.add(0, root.val);
                res.add(cur);
            }
        }
        if (root.right != null) {
            rightRes = pathSum(root.right, targetSum - root.val);
            for (int i = 0; i < rightRes.size(); i++) {
                List<Integer> cur = rightRes.get(i);

                cur.add(0, root.val);
                res.add(cur);
            }
        }
        // 4. 清理当前层 
        return res;
    }
}

力扣129

image.png

class Solution {
    /**
        1. sumNumbers(TreeNode root) 代表从root到叶子节点的所有数字之和;
        2. 不满足递归函数的定义,选取其作为递归函数
        3. 需要把其路径保留下来;
     */
    public int sumNumbers(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int res = 0;
        List<List<Integer>> lists = recur(root);
        for (int i = 0; i < lists.size(); i++) {
            int tem = 0;
            int length = lists.get(i).size();
            for (int j = 0; j < lists.get(i).size(); j++) {
                tem = tem + (int)(lists.get(i).get(j) * Math.pow(10 ,length - 1 - j));
            }
            res = res + tem;
        }
        return res;
    }

    public List<List<Integer>> recur(TreeNode root) {
        // 1. 结束递归
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if (root == null) {
            return res;
        }
        if (root != null && root.left == null && root.right == null) {
            List<Integer> cur = new ArrayList<Integer>();
            cur.add(0, root.val);
            res.add(cur);
            return res;
        }
        // 2. 处理当前层
        // 3. 递归下一层
        List<List<Integer>> leftRes = recur(root.left);
        List<List<Integer>> rightRes = recur(root.right);

        for (int i = 0; i < leftRes.size(); i++) {
            List<Integer> cur = leftRes.get(i);
            cur.add(0, root.val);
            res.add(cur);
        }
        for (int i = 0; i < rightRes.size(); i++) {
            List<Integer> cur = rightRes.get(i);
            cur.add(0, root.val);
            res.add(cur);
        } 
        return res;       
        // 4. 清理当前层
    }
}

这个题目要注意,本身sumNumbers(TreeNode root) 是不具备递归函数的条件的;

以 [1,2,3,4,5,6,7]为例子 先讨论[2,4,5]分支,它的结果是24 + 25 = 49;

再讨论[3,6,7]分支,它的结果是36 + 37 = 73;

然后在讨论[1,49,73] 结果为:149 + 173 很显然,不符合题意;

它是需要把路径都保留下来,假设先保留到list中,然后,再进行处理;所以,需要自己定义函数recur();
力扣235

image.png

class Solution {
    /**
     * 这个函数是满足递归函数的定义的
     * 1. 首先,要知道二叉搜索树的定义,左子树比根节点小右子树比根节点大;
     * 2. 当p q 的值都大于root的值时,说明根节点在右子树;
     * 3. 当p q 的值都小于root的值时,说明根节点在左子树;
     * 4. 当其他情况的时候,说明在两边,直接返回根节点即可;
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 1.结束递归
        // 2.处理当前层
        // 3.递归下一层
        int max = Math.max(p.val, q.val);
        int min = Math.min(p.val, q.val);
        if (root.val > max) {
            return lowestCommonAncestor(root.left, p, q);
        } else if (root.val < min) {
            return lowestCommonAncestor(root.right, p, q);
        } else {
            return root;
        }
        // 4.清理当前层
    }
}

力扣98

image.png
注意:本题的话,isValidBST(TreeNode root) 是不满足递归函数定义的;在判断的时候,需要有一个最大值和最小值;来辅助判断;所以,需要自己构造递归函数

class Solution {
    /**
     * 1.首先,要先判断,isValidBST(TreeNode root)是否满足递归条件
     * 2. 如果,左子树小于根节点;然后,根节点小于右子树,且左右子树都是二叉搜索树,能不能判断是不是二叉树呢?
     * 3. 结论是不行的,假设,[5,2,6,1,8]这个,就满足上面条件,但不是二叉搜索树;
     * 4. 所以,我们要重新定义递归条件
     */
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        return recur(root, null, null);
    }
    /**
     * 
     */
    public boolean recur(TreeNode root, Integer min, Integer max) {
        // 1. 结束递归
        int cur = root.val;
        if (min != null && cur <= min) {
            return false;
        }
        if (max != null && cur >= max) {
            return false;
        }

        if (root.left != null && root.left.val >= cur) {
            return false;
        }
        if (root.right != null && root.right.val <= cur) {
            return false;
        }

        // 2.处理当前层
        // 3.递归下一层
        if (root.left != null && root.right == null) {
            return recur(root.left, min, cur);
        } 
        if (root.right != null && root.left == null) {
            return recur(root.right, cur, max);
        }
        if (root.left == null && root.right == null) {
            return true;
        }
        return recur(root.left, min, cur) && recur(root.right, cur, max);
        // 4. 清理当前层
    }
}

力扣450

image.png
首先,先确定一下,deleteNode(TreeNode root, int key) 这个函数,是否可以当作递归函数;很显然,这个函数,返回的是删除key之后的二叉搜索树,所以,其实是可以当作递归函数的;

class Solution {
    /**
     *  1. 先判断能不能deleteNode()函数能不能充当递归函数
     *  2. 如果,root.val < key 那么,就从左边进行删除;
     *  3. 如果, root.val > key 那么,就从右边进行删除
     *  4. 如果,root.val == key 那么,就删除当前节点,然后,如果,右子树存在,就找到右子树的最小节点来进行替代
     *     右子树不存在的话,就左子树直接连接上; 
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        // 1. 结束递归
        if (root == null) {
            return null;
        }

        // 2. 处理当前层
        if (root.val == key) {
            // 先找到当前root的右子树中的最小值,当作新的根节点;
            if (root.right != null) {
                TreeNode tem = root.right;
                TreeNode leftRoot = root.left;
                TreeNode rightRoot = root.right;
                root.left = null;
                root.right = null;
                while (tem.left != null) {
                    tem = tem.left;
                }
                
                TreeNode cur = new TreeNode(tem.val);
                cur.left = leftRoot;
                cur.right = rightRoot;
                // 需要把最后一个进行删除
                cur.right = deleteNode(cur.right, cur.val);
                return cur;
            } else {
                return root.left;
            }
        // 3. 递归下一层
        } else if (root.val > key) {
            root.left = deleteNode(root.left, key);
        } else {
            root.right = deleteNode(root.right, key);
        }
        return root;
        // 4. 清理当前层
    }
}

力扣108

image.png

class Solution {
    /**
        1. 首先,先判定一下,这个函数是否符合递归函数定义;
        2. 给一个数组,然后,转为二叉搜索树,是可以满足递归定义的
        3. 因为,我取中间的为根节点,则根节点的左子树 是数组[left, mid - 1]生成的二叉搜索树,根节点的右子树是[mid + 1, right]生成的二叉搜索树
        4. 就可以了;然后,确定一下递归结束条件即可;
     */
    public TreeNode sortedArrayToBST(int[] nums) {
        // 1.结束递归条件
        int length = nums.length;
        if (length == 0) {
            return null;
        }
        // 2.处理当前层
        int left = 0;
        int right = nums.length - 1;
        int mid = left + (right - left) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        if (right - mid <= 1) {
            if (left != mid) {
                TreeNode leftNode = new TreeNode(nums[left]);
                root.left = leftNode; 
            }
            if (right != mid) {
                TreeNode rightNode = new TreeNode(nums[right]);
                root.right = rightNode;
            }
            return root;
        }
        int[] leftArray = new int[mid - left];
        int[] rightArray = new int[right - mid];
        for (int i = 0; i < mid; i++) {
            leftArray[i] = nums[i];
        }
        int j = 0;
        for (int i = mid + 1; i <= right; i++) {
            rightArray[j++] = nums[i];
        }
        // 3.递归下一层
        root.left = sortedArrayToBST(leftArray);
        root.right = sortedArrayToBST(rightArray);
        return root;
        // 4.清理当前层
    }
}

注意,上面代码是使用sortedArrayToBST(int[] nums)作为递归函数的,但是,由于要传入int[] 所以,需要创建一些新的空间;那么,如果,我们重复使用int[] nums 然后,使用left ,rigth 来限制取值也是可以的,就是需要重新定义递归函数;

class Solution {
    /**
        重新创建一个递归函数;
     */
    public TreeNode sortedArrayToBST(int[] nums) {
        
        int length = nums.length;
        if (length == 0) {
            return null;
        }
        
        return recur(nums, 0, length - 1);
        
    }

    public TreeNode recur(int[] nums, int left, int right) {
        // 1.结束递归条件
        int mid = left + (right - left) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        if (right - left <= 2) {
            if (mid != left) {
                TreeNode leftNode = new TreeNode(nums[left]);
                root.left = leftNode;
            }
            if (mid != right && right > 0) {
                TreeNode rightNode = new TreeNode(nums[right]);
                root.right = rightNode;
            }
            return root;
        }
        // 2.处理当前层
        // 3.递归下一层
        root.left = recur(nums, left, mid - 1);
        root.right = recur(nums, mid + 1, right);
        return root;
        // 4.清理当前层
    }
}

力扣236

image.png

class Solution {
    /**
      找到一个就返回当前节点
      root 的左节点返回了,右节点也返回了,那么就说明root是最近的根节点;
      root 只有一个节点返回,说明,其他的在该节点后面,那么,该节点就是最终解;
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 1.结束递归
        if(root == null || p == root || q == root)return root;
        // 2.处理当前层
        // 3.递归下一层
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left!=null && right != null)return root;
        if (left == null) {
            return right;
        } else {
            return left;
        }
        // 4.清理当前层;
    }
}