5 二叉树的常见问题(递归+迭代 应用)
5.1 判断二叉树是否对称(101. 对称二叉树)
5.1.1 解法一:迭代解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while(!queue.isEmpty()){
TreeNode leftnode = queue.poll();
TreeNode rightnode = queue.poll();
if(leftnode==null&&rightnode==null){
continue;
}
if(leftnode==null||rightnode==null||(leftnode.val!=rightnode.val)){
return false;
}
queue.offer(leftnode.left);
queue.offer(rightnode.right);
queue.offer(leftnode.right);
queue.offer(rightnode.left);
}
return true;
}
}
5.1.2 解法二:递归解法
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return compare(root.left,root.right);
}
public boolean compare(TreeNode left,TreeNode right){
if(right==null&&left!=null) return false;
else if(left==null&&right!=null) return false;
else if(left==null&&right==null) return true;
else if(left.val!=right.val) return false;
boolean res1 = compare(left.left,right.right);
boolean res2 = compare(left.right,right.left);
return res1&&res2;
}
}
5.2 求树的最大深度
5.2.1 104. 二叉树的最大深度
JAVA解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
int res = 0;
if(root!=null) queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode node = queue.poll();
if(i==size-1) res+=1;
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
}
}
return res;
}
}
递归解法:
class Solution {
public int maxDepth(TreeNode root) {
return getDepth(root);
}
public int getDepth(TreeNode node){
if(node==null) return 0;
int leftdep = getDepth(node.left);
int rightdep = getDepth(node.right);
int res = 1+Math.max(leftdep,rightdep);
return res;
}
}
5.2.2 559. N 叉树的最大深度
JAVA解法:
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public int maxDepth(Node root) {
Queue<Node> queue = new LinkedList<>();
int res = 0;
if(root!=null) queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
res++;
for(int i=0;i<size;i++){
Node node = queue.poll();
for(Node ch : node.children){
queue.offer(ch);
}
}
}
return res;
}
}
递归解法:
class Solution {
public int maxDepth(Node root) {
if(root==null) return 0;
int res = 0;
for(Node ch:root.children){
res = Math.max(res,maxDepth(ch));
}
return res+1;
}
}
5.3 求树的最小深度
5.3.1 111. 二叉树的最小深度
Java解法:
解法一:层次遍历
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
int mindep = 0;
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
mindep++;
int flag = 0;
for(int i=0;i<size;i++){
TreeNode node = queue.poll();
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
if(node.left==null&&node.right==null){
flag=1;
break;
}
}
if(flag==1) break;
}
return mindep;
}
}
解法二:递归求解
class Solution {
public int minDepth(TreeNode root) {
if(root==null) return 0;
int res = 0;
int leftdep = minDepth(root.left);
int rightdep = minDepth(root.right);
if(root.left==null&&root.right!=null) return 1+rightdep;
else if(root.left!=null && root.right==null) return 1+leftdep;
else return 1+Math.min(leftdep,rightdep);
}
}
5.4 判断树是否平衡
5.4.1 110. 平衡二叉树
递归三步曲分析:
明确递归函数的参数和返回值
参数的话为传入的节点指针,就没有其他参数需要传递了,返回值要返回传入节点为根节点树的深度。
那么如何标记左右子树是否差值大于1呢。
如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。
所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。
代码如下:
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度 int getDepth(TreeNode* node)
明确终止条件
递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的高度为0
代码如下:
if (node == NULL) { return 0; }
明确单层递归的逻辑
如何判断当前传入节点为根节点的二叉树是否是平衡二叉树呢,当然是左子树高度和右子树高度相差。
分别求出左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则则返回-1,表示已经不是二叉树了。
代码如下:
int leftDepth = depth(node->left); // 左 if (leftDepth == -1) return -1; int rightDepth = depth(node->right); // 右 if (rightDepth == -1) return -1; int result; if (abs(leftDepth - rightDepth) > 1) { // 中 result = -1; } else { result = 1 + max(leftDepth, rightDepth); // 以当前节点为根节点的最大高度 } return result;
代码精简之后如下:
int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; int rightDepth = getDepth(node->right); if (rightDepth == -1) return -1; return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
此时递归的函数就已经写出来了,这个递归的函数传入节点指针,返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树,则返回-1。
来自 【代码随想录】
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
int res = getDepth(root);
if(res == -1) return false;
else return true;
}
public int getDepth(TreeNode node){
if(node==null) return 0;
int leftdepth = getDepth(node.left);
if(leftdepth == -1) return -1;
int rightdepth = getDepth(node.right);
if(rightdepth == -1) return -1;
int res = 0;
if(Math.abs(leftdepth-rightdepth)>1) return -1;
else{
res = 1+Math.max(leftdepth,rightdepth);
}
return res;
}
}
5.5 257. 二叉树的所有路径
使用递归解题。注意以下几点:
一是终止条件为:当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。就开始加入当前路径,并开始回溯。
为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。
再来看一下终止处理的逻辑。
这里使用List结构path来记录路径,所以要把List结构的path转为string格式,在把这个string 放进 res里。
二是前序遍历——确定单层递归逻辑
因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进path中。
path.add(node.val);
然后是递归和回溯的过程,上面说过没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。
所以递归前要加上判断语句,下面要递归的节点是否为空.
三是注意加回溯语句。
递归与回溯是一体的。因此决不能只回溯一次。回溯需要和左右子树的递归语句写在一起。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if(root==null) return res;
getData(root,res,path);
return res;
}
public void getData(TreeNode node,List<String> res,List<Integer> path){
path.add(node.val);
if(node.left==null && node.right == null){
String tmp = "";
for(int i=0;i<path.size()-1;i++){
tmp+=path.get(i);
tmp+="->";
}
tmp+=path.get(path.size()-1);
res.add(tmp);
return;
}
if(node.left!=null){
getData(node.left,res,path);
path.remove(path.size()-1);
}
if(node.right!=null){
getData(node.right,res,path);
path.remove(path.size()-1);
}
}
}
5.6 404. 左叶子之和
「判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。」
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:
if(node.left!=null && node.left.left==null && node.left.right==null){
左叶子节点处理逻辑
}
迭代法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
if(root!=null) st.push(root);
int res = 0;
while(!st.empty()){
TreeNode node = st.pop();
if(node.left!=null && node.left.left==null && node.left.right==null){
res+=node.left.val;
}
if(node.left!=null) st.push(node.left);
if(node.right!=null) st.push(node.right);
}
return res;
}
}
递归法:
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root==null) return 0;
int leftsum = sumOfLeftLeaves(root.left);
int rightsum = sumOfLeftLeaves(root.right);
int midsum = 0;
if(root.left!=null && root.left.left==null && root.left.right==null){
midsum+=root.left.val;
}
return midsum+leftsum+rightsum;
}
}
5.7 513. 找树左下角的值
首先要是最后一行,然后是最左边的值。
本题使用层序遍历再合适不过了,只需要记录最后一行第一个节点的数值就可以了。
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if(root!=null) queue.offer(root);
int res = 0;
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode node = queue.poll();
if(i==0) res = node.val;
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
}
}
return res;
}
}
5.8 递归遍历路径
递归什么时候需要返回值什么时候不需要?
「如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。」
5.8.1112. 路径总和
1.这道题遍历时,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
2.如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
3.确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
return transfer(root,targetSum-root.val);
}
public boolean transfer(TreeNode node,int count){
if(node.left==null && node.right==null && count==0) return true;
if(node.left==null && node.right==null) return false;
if(node.left!=null){
count-=node.left.val;
if(transfer(node.left,count)) return true;
count+=node.left.val;
}
if(node.right!=null){
count-=node.right.val;
if(transfer(node.right,count)) return true;
count+=node.right.val;
}
return false;
}
}
5.8.2 113. 路径总和 II
下面代码中踩了一个坑:
一开始在递归中满足条件时,将path加入到res中,写的是:res.add(path);
但是导致res集合始终是空的。
改成:res.add(new ArrayList<>(path)); 之后正常运行得出结果。
这里主要是因为:path是引用类型,直接加的话,从始至终加入的都是同一个path,值没有改变。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root==null) return res;
path.add(root.val);
getPath(root,targetSum-root.val);
return res;
}
public void getPath(TreeNode node,int count){
if(node.left==null && node.right==null && count==0) {
res.add(new ArrayList<>(path));
return;
}
if(node.left==null && node.right==null) return;
if(node.left!=null){
path.add(node.left.val);
count-=node.left.val;
getPath(node.left,count);
count+=node.left.val;
path.remove(path.size()-1);
}
if(node.right!=null){
path.add(node.right.val);
count-=node.right.val;
getPath(node.right,count);
count+=node.right.val;
path.remove(path.size()-1);
}
return;
}
}
改进简洁写法:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
getPath(root,targetSum);
return res;
}
public void getPath(TreeNode node,int count){
if(node==null) return;
path.add(node.val);
count-=node.val;
if(node.left==null && node.right==null && count==0){
res.add(new ArrayList(path));
}
getPath(node.left,count);
getPath(node.right,count);
path.remove(path.size()-1);
return;
}
}
5.9 构建二叉树(重要)
5.9.1 106. 从中序与后序遍历序列构造二叉树
主要思路:以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间
下面是根据上面的思路为基础完成的代码,不是很简洁但是很清晰:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode root = null;
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0 && postorder.length==0) return null;
return helper(inorder,postorder);
}
public TreeNode helper(int[] inorder,int [] postorder){
if(postorder.length==0) return null;
int rootval = postorder[postorder.length-1];
TreeNode root = new TreeNode(rootval);
if(postorder.length==1) return root;
int ind ;
for(ind = 0;ind<inorder.length;ind++){
if(inorder[ind]==rootval) break;
}
//切割中序数组
int n1 = ind;
int n2 = ind;
int [] leftin = new int [ind];
int [] rightin = new int [inorder.length-ind-1];
for(int i=0;i<ind;i++){
leftin[i] = inorder[i];
}
n1++;
for(int j=0;j<inorder.length-ind-1;j++){
rightin[j] = inorder[n1++];
}
//切割后序数组
int [] leftpost = new int [ind];
int [] rightpost = new int [inorder.length-ind-1];
for(int i=0;i<ind;i++){
leftpost[i] = postorder[i];
}
for(int j=0;j<inorder.length-ind-1;j++){
rightpost[j] = postorder[n2++];
}
//递归
root.left = helper(leftin,leftpost);
root.right = helper(rightin,rightpost);
return root;
}
}
简洁代码版本:
在处理之前,使用HashMap处理中序遍历结果的信息。之后就可以直接访问键值来定位元素。不用重复赋值给数组。
即变化的只有下标,元素值没有变化。
class Solution {
HashMap<Integer,Integer> map;
public TreeNode buildTree(int[] inorder, int[] postorder) {
map = new HashMap<>();
for(int i=0;i<inorder.length;i++){
map.put(inorder[i], i);
}
return helper(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1);
}
public TreeNode helper(int[] inorder, int inLeft, int inRight, int[] postorder, int postLeft, int postRight){
//终止条件
if(inLeft > inRight || postLeft > postRight) return null;
//从后序遍历中拿到根节点
int root = postorder[postRight];
//从中序取出根节点的索引
int index = map.get(root);
//构建根节点
TreeNode tree = new TreeNode(root);
tree.left = helper(inorder, inLeft, index-1, postorder, postLeft, postRight-inRight+index-1);
tree.right = helper(inorder, index+1, inRight, postorder, postRight-inRight+index, postRight-1);
return tree;
}
}
5.9.2 105. 从前序与中序遍历序列构造二叉树
注意:
不管是后序还是前序,都必须根据中序左子树长度去定义自己的左子树长度。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
HashMap<Integer,Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map = new HashMap<>();
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return helper(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
public TreeNode helper(int [] preorder,int prest,int preend,int [] inorder,int inst,int inend){
if(inst>inend || prest>preend) return null;
int rootval = preorder[prest];
int ind = map.get(rootval);
TreeNode root = new TreeNode(rootval);
int leftnum = ind-inst;
root.left = helper(preorder,prest+1,prest+leftnum,inorder,inst,ind-1);
root.right = helper(preorder,prest+leftnum+1,preend,inorder,ind+1,inend);
return root;
}
}
5.9.3 654. 最大二叉树
单层递归的逻辑
-
先要找到数组中最大的值和对应的下表, 最大的值构造根节点,下标用来下一步分割数组。
-
最大值所在的下表左区间 构造左子树 这里要判断maxValueIndex > 0,因为要保证左区间至少有一个数值。
-
最大值所在的下表右区间 构造右子树 判断maxValueIndex < (nums.size() - 1),确保右区间至少有一个数值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
if(nums.length==1){
return new TreeNode(nums[0]);
} else if(nums.length==0) return null;
int maxval = 0;
int maxind = 0;
for(int i=0;i<nums.length;i++){
if(nums[i]>maxval){
maxval = nums[i];
maxind = i;
}
}
TreeNode root = new TreeNode(maxval);
if(maxind>0){
root.left = constructMaximumBinaryTree(Arrays.copyOfRange(nums,0,maxind));
}
if(maxind<nums.length-1){
root.right = constructMaximumBinaryTree(Arrays.copyOfRange(nums,maxind+1,nums.length));
}
return root;
}
}
5.10 合并两个二叉树
5.10.1 617. 合并二叉树
1.确定递归函数的参数和返回值:
首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
2.确定终止条件:
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。
反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
3.确定单层递归的逻辑:
单层递归的逻辑就比较好写了,这里我们用重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
那么单层递归中,就要把两棵树的元素加到一起。
接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。
t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。
最终t1就是合并之后的根节点。
前序遍历解法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null) return t2;
if(t2==null) return t1;
t1.val +=t2.val;
t1.left = mergeTrees(t1.left,t2.left);
t1.right = mergeTrees(t1.right,t2.right);
return t1;
}
}
迭代法解法:
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
Queue<TreeNode> queue = new LinkedList<>();
if(t1==null) return t2;
if(t2==null) return t1;
queue.offer(t1);
queue.offer(t2);
while(!queue.isEmpty()){
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
node1.val+=node2.val;
if(node1.left!=null && node2.left!=null){
queue.offer(node1.left);
queue.offer(node2.left);
}
if(node1.right!=null && node2.right!=null){
queue.offer(node1.right);
queue.offer(node2.right);
}
if(node1.left==null && node2.left!=null){
node1.left = node2.left;
}
if(node1.right==null && node2.right!=null){
node1.right = node2.right;
}
}
return t1;
}
}