一. 基础理论
1. 刷题大纲
2. 二叉树的定义
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;
}
}
3. 二叉树的分类
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。深度为k,有2^k-1个节点的二叉树。
完全二叉树
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
二叉搜索树
有序树、左小右大
平衡二叉搜索树
是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
4. 二叉树的遍历方式
个人体会
二叉树的题目就是选择正确的遍历方式,再加上题目的要求,以及某些特殊二叉树的性质,去解题。
4.1 深度优先遍历
- 前序遍历
- 中序遍历
- 后序遍历
4.2 广度优先遍历
层序遍历
相关题目 推荐
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度
二. 二叉树的遍历方式
1. 递归法
终止条件: null return
传递参数: root result
单层逻辑: 按一定顺序处理 遇到处理节点 add
T144. 前序遍历
中左右 经常有回溯的味道
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root,result);
return result;
}
public void preorder(TreeNode root,List<Integer> result){
if (root == null){return;}
result.add(root.val);
preorder(root.left,result);
preorder(root.right,result);
}
}
T145. 后序遍历
左右中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer> ();
postorder(root,result);
return result;
}
public void postorder(TreeNode root,List<Integer> result){
if (root == null){return;}
postorder(root.left,result);
postorder(root.right,result);
result.add(root.val);
}
}
T94. 中序遍历
左中右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer> ();
inorder(root,result);
return result;
}
public void inorder(TreeNode root,List<Integer> result){
if (root == null){return;}
inorder(root.left,result);
result.add(root.val);
inorder(root.right,result);
}
}
2. 迭代法
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
因此可以利用栈实现二叉树的遍历。
总结
弄清楚入栈和出栈顺序,动画参考添加链接描述
前序和后序逻辑一致,后序需要再做一个反转操作。
中序遍历,访问和处理不一致(深刻理解这句话), 引入指针进行判断
若分析不清楚 可画图帮助分析
T144. 前序遍历
遍历顺序: 中左右
入栈: 中右左
出栈: 中左右
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<>();//创建一个栈
if (root == null){return result;}
stack.push(root);//把根节点入栈
while (!stack.isEmpty() ){//只要栈不为空
TreeNode temp = stack.pop();
result.add(temp.val);//取出栈的最后一个元素
if (temp.right != null){
stack.push(temp.right);//右
}
if (temp.left != null){
stack.push(temp.left);//左
}
}
return result;
}
}
T145. 后序遍历
遍历顺序: 左右中
在前序基础上左,入栈 中左右 ,出栈为 中右左,反转为 左右中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){return result;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode temp = stack.pop();
result.add(temp.val);
if (temp.left != null){
stack.push(temp.left);
}
if( temp.right != null){
stack.push(temp.right);
}
}
Collections.reverse(result);
return result;
}
}
T94. 中序遍历
遍历顺序:左中右
但是先处理的是中节点,先访问的是左节点,造成处理顺序和访问顺序是不一致的。
root就是中节点,想办法解决访问和处理不一致的问题,故引入指针cur,进行判断,只要左空了,那就处理中,再访问右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer> ();
Stack<TreeNode> stack = new Stack<>();
if (root==null){return result;}
TreeNode cur = root;
//分别对左右子树是否为空进行判断
//先把左树放进栈里,若左空了就弹出,开始处理中之后,访问右
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;//访问左
}
else{//左子树空了就把stack弹出
cur = stack.pop();
result.add(cur.val);//处理中
cur = cur.right;//访问右
}
}
return result;
}
}
3. 统一迭代法
前面的迭代法,写法风格不一致,为了解决该问题,提出以下方法
将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记
取出栈的最后一个元素,遇到null,则先删除null,再删除最后一个元素,并将其值放入result,不是null,则根据遍历顺序放入null
T94. 中序遍历
//遍历顺序:左中右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer> ();
Stack<TreeNode> stack = new Stack<>();
if (root!=null){
stack.push(root);}
while (!stack.isEmpty()){
TreeNode node = stack.peek();
if (node != null){
stack.pop();
if(node.right!=null){stack.push(node.right);}//右
stack.push(node);
stack.push(null);//未处理的加null标记
if(node.left != null) {stack.push(node.left);}//左
}
else{
stack.pop();//删除空白后处理
node = stack.peek();
stack.pop();
result.add(node.val);
}
}
return result;
}
}
4. 层序遍历
4.1 迭代法
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑
两个while循环 一个对整个队列判断,另外一个判断每一层,以len记录每一层的元素个数,temp变量记录每一层的结果,入队顺序为左右
class Solution {
//迭代法
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if (root == null){return result;}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while( !que.isEmpty()){
List<Integer> temp = new ArrayList<Integer>();//每一层的变量
int len = que.size();//每一层的个数
//每一层的逻辑
while (len > 0){
TreeNode node = que.poll();
temp.add(node.val);
if(node.left != null){que.offer(node.left);}
if(node.right != null){que.offer(node.right);}
len--;
}
//每一层装完后加到result
result.add(temp);
}
return result;
}
}
4.2 递归法
单层逻辑: 记录深度,添加结果值
访问顺序:左右
class Solution {
List<List<Integer>> result = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
dfsOrder(root,0);
return result;
}
public void dfsOrder(TreeNode root,Integer deep){
if (root == null){return;}//递归终止条件
//单层逻辑
deep++; //遍历一次 深度加一
if (result.size()<deep){
List<Integer> temp = new ArrayList<Integer>();
result.add(temp);
}//给结果加上层次
result.get(deep-1).add(root.val);
dfsOrder(root.left,deep);
dfsOrder(root.right,deep);
}
}
三、二叉树的属性
T101. 对称二叉树 (后序遍历)
- 传入左右节点,返回布尔值
- 终止条件:根据左右节点的值 分类返回true或者false
- 单层逻辑:处理外侧、内侧(注意处理的节点)
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
class Solution {
public boolean isSymmetric(TreeNode root) {
return compareHelp(root.left,root.right);
}
public boolean compareHelp(TreeNode leftNode,TreeNode rightNode){
if (leftNode == null && rightNode != null){return false;}
if (leftNode != null && rightNode == null){return false;}
if (leftNode == null && rightNode == null){return true;}
if (leftNode.val != rightNode.val){return false;}
boolean compareLeft = compareHelp(leftNode.left,rightNode.right);
boolean compareRight = compareHelp(leftNode.right,rightNode.left);
return compareLeft&&compareRight;
}
}
相关题目
100.相同的树
572.另一个树的子树
T104. 二叉树的最大深度(后序遍历、层序)
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
- 本人更喜欢用层序遍历,因为有多少层就是深度
class Solution {//层序遍历
public int maxDepth(TreeNode root) {
if( root == null) {return 0;}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
int depth = 0;
while (!que.isEmpty()){
int length = que.size();
while (length>0){
TreeNode node = que.poll();
if(node.left != null) {que.offer(node.left);}
if (node.right != null) {que.offer(node.right);}
length--;
}
depth++;
}
return depth;
}
}
- 前序遍历
有回溯的味道
//前序遍历
//从上到下
class Solution {
int res;
public int maxDepth(TreeNode root) {
res = 0;
if(root == null ){return 0;}
getDepth(root,1);//深度从1开始
return res;
}
public void getDepth(TreeNode root,int depth){
res = Math.max(depth,res);//处理中节点
if(root.left==null&&root.right==null){
return; //终止条件
}
if(root.left!=null){
depth++;
getDepth(root.left,depth);
depth--;
}
if(root.right!=null){
depth++;
getDepth(root.right,depth);
depth--;
}
return;
}
}
相关题目
559. n叉树的最大深度
/*
// 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) {
if(root == null) {return 0;}
int depth = 0;//总共有多少层
Queue<Node> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int length = que.size();//每一层的大小
depth++;
while(length>0){
Node temp = que.poll();
for(int i = 0;i<temp.children.size();i++){
if(temp.children.get(i)!=null){
que.offer(temp.children.get(i));
}
}
length--;
}
}
return depth;
}
}
T111. 二叉树的最小深度(后序遍历)
求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
class Solution {
public int minDepth(TreeNode root) {
if (root == null){return 0;}
int leftDepth = minDepth(root.left);
int rightDepth = minDepth(root.right);
if (root.left == null){return rightDepth+1;} //与最大深度不同之处
if (root.right == null) {return leftDepth+1;}
return Math.min(leftDepth,rightDepth)+1;
}
}
//对比最大深度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {return 0;}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth,rightDepth)+1;
}
}
T222. 完全二叉树的节点个数(后序、层序、完全二叉树的性质)
- 方法一: 后序遍历
class Solution {
public int countNodes(TreeNode root) {
if (root == null) {return 0;}
int leftNum = countNodes(root.left);
int rightNum = countNodes(root.right);
int num = leftNum + rightNum +1;
return num;
}
}
- 方法二: 层序遍历法
class Solution {
public int countNodes(TreeNode root) {
int num = 0;
if (root == null) {return num;}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while( !que.isEmpty()){
int length = que.size();
while(length > 0){
TreeNode node = que.poll();
num++;
if(node.left != null){que.offer(node.left);}
if(node.right != null) {que.offer(node.right);}
length--;
}
}
return num;
}
}
- 方法三: 利用完全二叉树的特点(后序遍历)
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。
class Solution {
public int countNodes(TreeNode root) {
if (root == null){return 0;}
int leftDepth = 0;
int rightDepth = 0;
TreeNode left = root.left;
TreeNode right = root.right;
while (left != null){
left = left.left;
leftDepth++;
}
while (right != null){
right = right.right;
rightDepth++;
}
//由于完全二叉树特点,左右深度一致必然为满二叉树
if (leftDepth == rightDepth){return (2<<leftDepth) - 1;}//终止条件
//不断地遍历必然会出现满二叉树
int leftNum = countNodes(root.left); //左
int rightNum = countNodes(root.right); //右
int res = leftNum + rightNum + 1; //中
return res;
}
}
T110. 平衡二叉树(高度问题)(后序遍历)
-
深度和高度的区别
前面求深度的时候,我们把它当作求根节点的高度了 故都用了后序遍历
后序遍历能够从下到上 前序是从上到下
-
代码实现(后序遍历)
class Solution {
public boolean isBalanced(TreeNode root) {
return getHight(root) != -1; //高度大于1返回-1
}
public int getHight(TreeNode root){//后序遍历
if (root == null) {return 0;}
int leftHigh = getHight(root.left);//左
if (leftHigh == -1){return -1;}//一个子树不符合,整体不符合
int rightHigh = getHight(root.right);//右
if (rightHigh == -1) {return -1;}
if (Math.abs(leftHigh-rightHigh)>1){return -1;}
return Math.max(leftHigh,rightHigh) + 1;
}
}
T257. 二叉树的所有路径 (前序遍历)
- 从下到上 采用前序遍历,同时需要回溯思想
- 做题思路
需要所有路径,且从题目可以看出要从上到下遍历,因此选择前序遍历 - 递归三部曲:
参数和返回值:path和root,返回空
终止条件:左右都空
单层处理逻辑: 把值加入res,左右节点
class Solution {
List<String> res = new ArrayList<String>();
public List<String> binaryTreePaths(TreeNode root) {
if (root == null){
return res;
}
List<Integer> path = new ArrayList<Integer>();
pathHelper(root,path);
return res;
}
public void pathHelper(TreeNode root,List<Integer> path){
//处理中节点
path.add(root.val);
if(root.left==null && root.right==null){//终止条件
StringBuilder sb = new StringBuilder();
for(int i = 0;i<path.size()-1;i++){
sb.append(path.get(i)).append("->");
}
sb.append(path.get(path.size()-1));
//如何构建字符串:先用一个list把val加进来,然后每个值中间插入->
res.add(sb.toString());
}
//左
if(root.left!=null){
pathHelper(root.left,path);
path.remove(path.size()-1);
}
//右
if(root.right!=null){
pathHelper(root.right,path);
path.remove(path.size()-1);
}
}
}
T112. 路径总和 (前序)
- 问题分析
参数和返回值:根节点和计算器,布尔值
终止条件:找到了叶节点且count为0
单层逻辑:处理count;处理左节点;处理右节点
注意只有左节点不是空才处理,如果得到了true及时返回不需要继续遍历 - 代码实现
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {return false;}
return pathHelper(root,targetSum);
}
public boolean pathHelper(TreeNode root,int count){
count = count - root.val;
if (root.left == null && root.right == null && count == 0){
return true;//终止条件
}
if (root.left != null){//左不为空
boolean left = pathHelper(root.left,count);
if(left) {return true;}//找到了
}
if (root.right != null){
boolean right = pathHelper(root.right,count);
if(right) {return true;}
}
return false;
}
}
T113. 路径总和Ⅱ
- 与T112对比
需要找出所有路径,因此需要回溯
要返回具体路径,需要List<List<Integer>> 来保存结果 - 代码实现
class Solution {
int sum = 0;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
if(root == null){return res;}
LinkedList<Integer> path = new LinkedList<>();
pathHelper(root,targetSum,res,path);
return res;
}
//前序遍历 中左右
public void pathHelper(TreeNode root,int targetSum,List<List<Integer>> res,LinkedList<Integer> path){
path.add(root.val);
sum += root.val;
if(root.left == null && root.right == null){//找到叶子节点
if(sum == targetSum){
res.add(new ArrayList<>(path));//和为目标值 就把结果加进去
}
return;
}
if(root.left != null){
pathHelper(root.left,targetSum,res,path);
int leftVal = path.removeLast();
sum -= leftVal;
}
if(root.right != null){
pathHelper(root.right,targetSum,res,path);
int rightVal = path.removeLast();
sum -= rightVal;
}
}
}
T404. 左叶子之和 (后序遍历)
- 做题思路
节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点
递归顺序:由于要取递归函数的返回值求和 所以采用左右中 (这么做的话,在处理中的时候已经有了左和右两个值)
终止条件:空节点
单层逻辑:需要处理判断是否为左叶子的逻辑,进行加和 - 代码实现
//后序遍历:返回和
class Solution {
int sum = 0;
public int sumOfLeftLeaves(TreeNode root) {
return sumHelper(root,0);
}
public int sumHelper(TreeNode root,int sum){
if(root==null){return 0;}
int leftSum = sumHelper(root.left,sum);//左
int rightSum = sumHelper(root.right,sum);//右
if(root.left!=null&&root.left.left==null&&root.left.right==null){//判断是否为左叶子
sum += root.left.val;
}
return leftSum+rightSum+sum;//处理中
}
}
T513. 找树左下角的值(层序、前序)
- 方法一:层序遍历
最后一层的第一个元素
get直接获取元素
class Solution {
public int findBottomLeftValue(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while (!que.isEmpty()){
List<Integer> path = new ArrayList<Integer>();
int len = que.size();
while(len > 0){//每一层的遍历
TreeNode node = que.poll();
path.add(node.val);
if (node.left != null){ que.offer(node.left);}
if (node.right != null){ que.offer(node.right);}
len--;
}
result.add(path);
}
int value = result.get(result.size()-1).get(0);
return value;
}
}
- 方法二:前序遍历(不好理解)
最大深度的第一个元素
//前序遍历
//当到达最大深度时,取第一个元素
class Solution {
int value = 0;
int Deep = -1;
public int findBottomLeftValue(TreeNode root) {
findHelper(root,0);
return value;
}
public void findHelper(TreeNode root,int deep){
if(root==null){return ;}
if(root.left == null && root.right==null){
if(deep>Deep){
value = root.val;
Deep = deep;
}
}
if(root.left != null){findHelper(root.left,deep+1);}
if(root.right != null){findHelper(root.right,deep+1);}
}
}
T543. 二叉树的直径
class Solution {
int res;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return res-1;
}
public int dfs(TreeNode node){
if(node == null) return 0;
int left = dfs(node.left);
int right = dfs(node.right);
res = Math.max(res,left+right+1);
return Math.max(left,right)+1;
}
}
四、二叉树的修改与构造
T226. 翻转二叉树(前序、后序)
- 参数:根节点,返回值:空
- 终止条件:根节点为空
- 单层逻辑:翻转左、右
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null ) {return null;}
swap(root);
invertTree(root.left);
invertTree(root.right);
return root;
}
public void swap(TreeNode node){
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
}
T106. 从中序与后序遍历序列构造二叉树(前序)
- 思路
找到后序数组最后一个元素在中序数组的位置,作为切割点
切割中序数组,切成中序左数组和中序右数组
切割中序数组,切成中序左数组和中序右数组
递归处理左区间和右区间
- 代码
class Solution {
Map<Integer,Integer> map = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
for (int i = 0;i<inorder.length;i++){
map.put(inorder[i],i);//存放中序数组
}
//区间遵循左闭右闭准则
return buildHelp(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
}
public TreeNode buildHelp(int[] inorder,int inBegin,int inEnd,int[] postorder,int postBeigin,int postEnd){
if (inBegin > inEnd || postBeigin > postEnd){return null;}//终止条件
//单层逻辑
int val = postorder[postEnd];//根节点的值
TreeNode root = new TreeNode(val);
int rootIndex = map.get(val);//根节点在中序数组的序号,
int lengthLeft = rootIndex - inBegin;//左子树的长度
root.left = buildHelp(inorder,inBegin,rootIndex-1,
postorder,postBeigin,postBeigin+lengthLeft-1);//处理左子树
root.right = buildHelp(inorder,rootIndex+1,inEnd,
postorder,postBeigin+lengthLeft,postEnd-1);
return root;
}
}
T105. 从前序与中序遍历序列构造二叉树
/**
* 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 {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
T654. 最大二叉树 (前序遍历)
- 思路
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树
参数:数组,起始和终止坐标;返回值:节点
终止条件:数组空
单层逻辑:找最大值,构造节点,处理左、右 - 代码
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return constructHelper(nums,0,nums.length-1);//左闭右闭
}
public TreeNode constructHelper(int[] nums,int start,int end){
if(end - start < 0 ){
return null;}//终止条件
int maxIndex = start;
int maxValue = nums[maxIndex];
for (int i =start+1;i<=end;i++){
if(nums[i]>maxValue){
maxValue=nums[i];
maxIndex=i;}
}//找最大值及坐标
TreeNode root = new TreeNode(maxValue);//根节点
root.left = constructHelper(nums,start,maxIndex-1);
root.right = constructHelper(nums,maxIndex+1,end);
return root;
}
}
T617. 合并二叉树 (前序操作)
- 思路
同时遍历两个树:其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作
参数:两个根节点;返回值:相加后的根节点
终止条件:其中一个根节点为空
单层逻辑:1和2相加,处理左、右 - 代码
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
//终止条件:哪个有值就返回哪个
if (root1 == null){return root2;}
if (root2 == null){return root1;}
//单层逻辑:前序遍历
root1.val += root2.val;
root1.left = mergeTrees(root1.left,root2.left);
root1.right = mergeTrees(root1.right,root2.right);
return root1;
}
}
五、二叉搜索树的属性
T98. 验证二叉搜索树 (中序遍历)*
- 思路分析
中序遍历实现了从小到大,一直比较,若出现大于当前值,那就返回false - 代码实现
class Solution {
TreeNode max;
public boolean isValidBST(TreeNode root) {
if (root==null){return true;}
boolean left = isValidBST(root.left);
if(!left){return false;}
if (max != null && max.val>=root.val ){
return false;
}
max = root;
boolean right = isValidBST(root.right);
if(!right){return false;}
return true;
}
}
T700. 二叉搜索树中的搜索(前序遍历)
- 思路分析
注意与普通树的区别,利用其特点,可以减少递归次数 - 代码实现
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val ==val){
return root;
}
if (root.val>val){
return searchBST(root.left,val);
}
else {
return searchBST(root.right,val);
}
}
}
T530. 二叉搜索树的最小绝对差 (中序遍历)
- 思路
把二叉树变成数组,再求
直接用pre记录上一个节点,进行计算
中序遍历:从小到大
技巧:用一个pre指针指向前一个元素
- 代码
//中序遍历出来是递增序列
class Solution {
int result = Integer.MAX_VALUE;
TreeNode temp;//记录上一个节点
public int getMinimumDifference(TreeNode root) {
if (root==null){return 0;}
getHelper(root);
return result;
}
public void getHelper(TreeNode root){
if (root==null){return;}
getHelper(root.left);//left
if (temp != null){
result = Math.min(result,root.val-temp.val);
}
temp = root;
getHelper(root.right);
}
}
T501. 二叉搜索树中的众数 (中序遍历)
- 思路
普通二叉树,进行遍历,用一个map统计所有频率,统计完后根据value排序
二叉搜索树,中序遍历出来就是一个有序数组,只要比较前面的元素即可;
稍微复杂一点的地方在于,对节点的处理逻辑:
传进来一个节点,判断是否第一次出现,若是count为1;若不是,count加1。
对count处理,如果是最大频率,那么把结果加入;若大于最大频率,更新最大频率,并且把结果清空,加入新的元素;更新指针 - 代码
class Solution {
//指针+记录频率变量
int count = 0;//用来计数
int maxCount = 0;//最大频率
ArrayList<Integer> result = new ArrayList<>();//存放结果
TreeNode prev = null;//引入指针 指向前一个元素
public int[] findMode(TreeNode root) {
findHelper(root);
int[] res = new int[result.size()];
for(int i=0;i<result.size();i++){
res[i]=result.get(i);
}
return res;
}
public void findHelper(TreeNode root){
if(root==null){return;}
findHelper(root.left);//处理左树
//记录count
if (prev == null|| prev.val != root.val) { count=1; }
else if (prev.val == root.val){count++;}
//判断count
if(count==maxCount){
result.add(root.val);
}
else if(count>maxCount){
maxCount=count;
result.clear();
result.add(root.val);
}
prev = root;//指针移动
findHelper(root.right);
}
}
T538. 把二叉搜索树转换为累加树 (反中序遍历)
- 思路分析
中序遍历:得到递增的序列,对于序列,就是等于后面的元素之和
如果采用右中左的顺序,那就可以转换为累加求和了 - 代码
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
convertHelper(root);
return root;
}
public TreeNode convertHelper(TreeNode root){
if (root == null){return null;}
//右中左
convertHelper(root.right);
sum = sum + root.val;
root.val = sum;
convertHelper(root.left);
return root;
}
}
六、二叉树的公共祖先问题
T236. 二叉树的最近公共祖先 (后序遍历)
- 思路分析
什么叫最近公共祖先?深度要大,即要尽可能在树的底部;
自然而然,想到自下而上搜索->后序遍历
递归参数和返回值:三个节点,找到p和q就返回 - 代码实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//终止条件
if (root==null){return null;}
if (root==p||root==q){return root;}//返回值必然是p或者q
//遍历左右
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
//处理中节点
if (left ==null && right == null) {return null;}
else if (left != null && right == null){return left;}//p和q在左树上
else if (left == null && right != null){return right;}
else{return root;}
}
}
T235. 二叉搜索树的最近公共祖先 (前序遍历)
- 思路分析
与上一题区别在于,二叉搜索树有序。因此本题不需要从下到上遍历
,所有 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。且一定是最近公共祖先(可以画图分析) - 代码实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val > p.val && root.val>q.val){return lowestCommonAncestor(root.left,p,q);}
if (root.val<p.val&&root.val<q.val){
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
}
七、二叉搜索树的修改与构造
T701. 二叉搜索树的插入操作(前序)
- 思路分析
只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。
因为要处理中节点,所以采取前序遍历。 - 代码实现
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null){
TreeNode node = new TreeNode(val);
return node;
}
//BST往往不需要遍历整个树
if(root.val>val){root.left=insertIntoBST(root.left,val);}
if(root.val<val){root.right=insertIntoBST(root.right,val);}
return root;
}
}
T450. 删除二叉搜索树的节点 (前序遍历)
- 思路分析
需要对传进来的节点进行判断,采用前序遍历(中左右)
二叉搜索树不需要遍历整个树,判断左右大小即可
难点在于: 处理逻辑四种情况,最复杂的是左右都存在 - 代码实现
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
//终止条件
if (root == null){return null;}
//找到了节点
if (root.val == key){
//1.左右树都为空 直接删除
if (root.left == null && root.right == null){
return null;
}
//2.左为空,右不为空:
if (root.left == null && root.right!=null){
return root.right;
}
//3.左不为空,右为空:
if (root.left != null && root.right == null){
return root.left;
}
//4.左右均非空:把左孩子移到右子树最左面节点的做孩子上
else if(root.left!=null&&root.right!=null){
TreeNode node = root.right;
while(node.left!=null){
node = node.left;
}
node.left = root.left;
root = root.right;
return root;
}
}
if (root.val > key){
root.left = deleteNode(root.left,key);
}
if (root.val < key){
root.right = deleteNode(root.right,key);
}
return root;
}
}
T669. 修剪二叉搜索树(前序遍历)
- 思路分析
删除0,把2赋给节点,因此递归函数需要返回值
前序遍历,对中节点处理 - 代码实现
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null){return null;}
//单层逻辑
if (root.val<low){//小于最小,遍历右树
return trimBST(root.right,low,high);
}
if (root.val>high){
return trimBST(root.left,low,high);
}
//处于区间之中
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
return root;
}
}
T108. 将有序数组转换为二叉搜索树
- 思路分析
核心思路: 数组的中间节点作为根节点,左区间作为左树,右区间作为右树
递归函数的参数: 数组,开始索引,结束索引 - 代码实现
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode node = sortedHelper(nums,0,nums.length-1);
return node;
}
public TreeNode sortedHelper(int[] nums,int left,int right){
if(left>right){return null;}
int mid = left + ((right-left) >> 1);
TreeNode node = new TreeNode(nums[mid]);
node.left = sortedHelper(nums,left,mid-1);
node.right = sortedHelper(nums,mid+1,right);
return node;
}
}