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
第一步:先判定系统给的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
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
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
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
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
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
-
左子树是平衡二叉树,右子树也是平衡二叉树
本身,这个isBalanced() 具备递归函数定义;然后,只需要使用high()来求出当前节点子树的最大高度即可;
力扣 112
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
注意哈,这里左叶子的定义,就算右子树被左子树挡住了;那么,右子树的左叶子也是符合条件的;
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
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
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
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
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
注意:本题的话,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
首先,先确定一下,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
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
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.清理当前层;
}
}