前言
哈哈哈,到二叉树啦!冲冲冲!!!
因为好久没看二叉树了,就先复习一下吧(下图全部来自代码随想录)。
对二叉树几个类型概念的简单介绍:
满二叉树
完全二叉树
二叉搜索树
平衡二叉搜索树(即AVL树)
对二叉树的两种存储形式进行简单介绍:
链式存储
顺序存储
用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
144. 二叉树的前序遍历(递归和迭代两种方式)
题目描述
递归方法思路分析:
很简单了,二叉树基础题目。
递归方式代码如下:
/**
* 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 {
//因为是递归遍历
//所以结果集合我们声明为全局变量
ArrayList<Integer> list = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
//二叉树前序遍历顺序:
//头节点、左节点、右节点
//如果当前节点为空,直接终止程序返回集合
if(root == null) return list;
//不为空,那么根据前序遍历顺序
//我们将该节点的值放入结果集合
list.add(root.val);
//然后递归进入左子树,先判断左子树不为空才能进入
if(root.left != null) preorderTraversal(root.left);
//再递归进入右子树,先判断右子树不为空才进入
if(root.right != null) preorderTraversal(root.right);
//返回结果集合
return list;
}
}
迭代法思路分析:
递归的本质其实就是用了计算机系统中的堆栈,堆栈也是一种栈结构,所以我们使用栈来进行递归的模拟是完全OK的,我们可以画图来分析这个过程。
首先有如下二叉树,然后我们还得有一个栈:
然后为了让栈里面能弹出东西,那么我们首先要向里面放入第一个节点,也就是我们的头节点:
因为我们现在要实现的是先序输出,是先输出头节点、再输出左节点、最后输出右节点,所以我们现在要先输出头节点1,打印:
现在看1节点是否有右节点(因为栈先进后出,如果我们后面想先打印左节点那么我们现在就应该先压右节点),有则压入,再看1节点是否有左节点,有则压入:
然后现在则又去弹出栈顶输出2,再判断2节点是否有右节点有就添加,再看是否有左节点,有就添加:
按上述则就构成了一个循环,它就能帮我们实现前序遍历,后面循环下去即可。
迭代代码如下:
/**
* 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<Integer> preorderTraversal(TreeNode root) {
//二叉树前序遍历顺序:
//头节点、左节点、右节点
//创建结果集合
ArrayList<Integer> list = new ArrayList<>();
//特判
if(root == null) return list;
//既然是用栈模拟系统堆栈,自然我们就需要一个栈
Stack<TreeNode> stack = new Stack<>();
//先往栈中放入一个当前树的头节点
stack.push(root);
//当栈不为空时,循环入栈出栈,模拟系统堆栈
while(!stack.isEmpty()){
//将栈顶的值取出赋值给一个变量,因为涉及到迭代
//所以该变量的值要一直变化
root = stack.pop();
//因为栈是先进后出,所以先进的肯定是最后一个出来
//但我们出来的顺序必须得是头、左、右
//所以我们先将头结点添加到结果集中,再加入它的右节点,然后才是左节点
list.add(root.val);
//如果右节点不为空,将右节点入栈
if(root.right != null) stack.push(root.right);
//如果左节点不为空,将左节点入栈
if(root.left != null) stack.push(root.left);
}
//返回结果集合
return list;
}
}
145. 二叉树后序遍历(递归和迭代两种方式)
题目描述:
递归思路分析:
一样没啥好分析的,都是基础操作。
递归代码如下:
/**
* 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 {
//因为是递归方式
//所以我们创建的结果集为全局变量
ArrayList<Integer> list = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
//后序遍历:先左节点、再右节点、最后头节点
//判断是否为空树
if(root == null) return list;
//不为空树,先递归遍历左子树
if(root.left != null) postorderTraversal(root.left);
//再递归遍历右子树
if(root.right != null) postorderTraversal(root.right);
//将结果添加进结果集
list.add(root.val);
//返回该结果集
return list;
}
}
迭代思路分析:
具体过程和上面的二叉树前序遍历分析是差不多少的,因为栈先进后出的特性,同时我们需要的后序遍历顺序为:先左节点、后右节点、最后才是头节点,那么由这两个条件我们就应该在压栈时执行先压头节点再压右节点最后再压左节点的顺序来,可是这样很麻烦。
但是我们会发现一个特性是,假如我们稍稍改变一下刚刚前序遍历的迭代实现中压栈的顺序,改为:先压头节点、再压左节点、最后才压右节点的话,那么得到的顺序正好就是我们要求的后序遍历的顺序的反过来的形式有没有!
知道了这个特性那其实就很好办了,不就是顺序是反的嘛,我们只要再用一个辅助栈来帮忙再弹正回来不就行了?
迭代代码如下:
/**
* 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<Integer> postorderTraversal(TreeNode root) {
//创建结果集合
ArrayList<Integer> list = new ArrayList<>();
//判断是否为空树
if(root == null) return list;
//我们需要两个栈,一个是用来模拟系统堆栈的
//另外一个则是辅助栈,用来帮我们弹正顺序的
Stack<TreeNode> stack = new Stack<>();//模拟系统栈
Stack<TreeNode> temp = new Stack<>();//辅助栈
//先往堆栈中放入一个根节点
stack.push(root);
//循环压栈出栈进行判断
while(stack.isEmpty() != true){//如果栈不为空
//取出栈顶值作为循环不变量,因为后续循环需要它来迭代
root = stack.pop();
//因为待会儿会有反转顺序的操作
//所以这里按照先序遍历的逻辑没有关系
//添加该元素进结果集前,先放入辅助栈中
temp.push(root);
//注意这里就和先序遍历不一样了,先放入左节点
if(root.left != null) stack.push(root.left);
//再放入右节点
if(root.right != null) stack.push(root.right);
}
//将辅助栈中的元素放入结果集中
while(temp.size()>0){
list.add(temp.pop().val);
}
//返回结果
return list;
}
}
94. 二叉树中序遍历(递归和迭代两种方式)
题目描述:
递归思路分析:
无。
递归代码如下:
/**
* 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 {
ArrayList<Integer> list = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null) return list;
if(root.left != null) inorderTraversal(root.left);
list.add(root.val);
if(root.right != null) inorderTraversal(root.right);
return list;
}
}
迭代思路分析:
这个稍微有点东西,核心逻辑如下:
1、我们先将整条左边界依次压入栈中
2、如果左边界已经压到了尽头无法再压,那么我们就打印输出该节点,然后又进入该节点的右节点继续压入该右树的整条左边界(包括头节点,“整条左边界的意思是人的肉眼可看见可理解的一条左边界上的线”)
我们可以画图来分析:
依然是有如下树,以及我们需要一个栈:
按照中序遍历要求输出顺序应该为左节点、头结点、右节点,那么按照每我们上面所说的逻辑,我们一进来先将整条左边界压入栈中:
(上图中的1、2、4连成的线就是我上面说的该树左边界线上的所有元素)
这时来到了临界条件:4节点的左子节点为空了,那么这时候就打印该节点,然后进入该节点的右子节点,但是4的右子节点依然为空,所以又回到2节点,打印2号节点,判断2节点右子节点为空吗?为空则继续往回弹栈,不为空则进入右子节点即5节点,又将其左边界上的所有元素压入栈(但是图中的树只有5本身):
通过上面分析其实循环已经出来了,自己画个图就能看明白的。
也可以这么理解,整个二叉树是被左边界分解成了如下图所示:
当我们依次把左边界压栈的时候,我们是先压头结点然后再把左边界后续所有压进去的,又因为是在栈里,所以我们可以认为,如果我们以左边界的视角来看整棵树的话,对于任何一条左边界,它弹出的顺序都是先左再头,当弹到头节点时,就再进入其右子树中继续上述操作即可,大体就是这么个流程。
至于算法实现部分,就反复写叭,写多了记住就好了,上面的就是帮助理解一下。
迭代代码如下:
/**
* 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<Integer> inorderTraversal(TreeNode root) {
//中序遍历顺序:先左节点、再头节点、最后右节点
//创建结果集合
ArrayList<Integer> list = new ArrayList<>();
//判断树是否为空
if(root == null) return list;
//创建我们需要的栈
Stack<TreeNode> stack = new Stack<>();
//栈不为空时,一直遍历,但因为一开始栈是没有元素的
//所以再加一个条件root非空来协助进入循环遍历吧
while(stack.isEmpty() != true || root != null){
//将一整个左边界压入栈中
if(root != null){
stack.push(root);//将左边界上的元素压入栈中
root = root.left; //不变量继续往下一个左边界上的元素走
}else{//如果当前节点的左节点为空,则表示左边界已经遍历完
//那么将该节点弹出赋值给root(root是循环不变量)
//并且添加到结果集中
root = stack.pop();//这一步其实就是返回上一节点了
list.add(root.val);
//进入右节点继续将该右节点子树的左边界上的所有元素压栈
root = root.right;
}
}
//返回结果集
return list;
}
}
102. 二叉树的层序遍历
题目描述:
思路分析:
二叉树的层序遍历其实就很像图中的BFS广度优先遍历,我们会用一个队列来记录遍历出来的节点。啥叫层序遍历?就是把二叉树中每一层的所有节点按一定顺序(比如从左到右)遍历出来,如第一层的层序遍历就是1,第三层的层序遍历就是4、5、6、7,如下:
具体层序遍历过程可以画图分析如下:
首先我们需要一棵树,然后还有一个队列:
然后我们先将树的根节点放入队列中,如下:
然后就循环判断当队列不等于空时,拿出一个就打印输出,然后把该输出节点的左节点加入队列,然后把右节点加入队列:
然后因为是循环嘛,又从头开始,此时队头为节点2,那么就将2节点弹出打印输出,然后又将其左节点和右节点加入队列中:
直至遍历结束就能解决,模板代码如下:
当然对于这题并非就只需要将上面讲的直接copy就可以通过,是需要有一些些改变的,但是大体思路差不多,代码中有详细注释。
代码如下:
/**
* 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<List<Integer>> levelOrder(TreeNode root) {
//这个不是很难,就类似于宽度优先遍历
//创建一个结果集
List<List<Integer>> res = new ArrayList<>();
//如果root为空,则为空树,返回即可
if(root == null) return res;
//创建一个队列
LinkedList<TreeNode> queue = new LinkedList<>();
//首先往队列里面放入一个根节点
queue.add(root);
//循环判断处理
while(queue.isEmpty() != true){//当队列不为空时,循环处理
//创建一个集合用来装载每一层的所有节点
ArrayList<Integer> list = new ArrayList<>();
//用len记录当前层的所有节点数(即队列长度),待会儿循环要用
int len = queue.size();
//循环将每一层的所有节点存入list的层结果集合中
for(int i=len; i>0;i--){
TreeNode temp = queue.poll();
list.add(temp.val);
//因为每一个节点都可能会有左右节点,所以每一次都要判断是否有左右节点
//然后将其加入队列,但是并不会造成多加
//因为有队列长度作循环次数限制
//又因为从左往右遍历,所以现在判断其是否有左子节点,将其加入队列中
if(temp.left != null) queue.add(temp.left);
//再判断其是否有右子节点,将其加入队列中
if(temp.right != null) queue.add(temp.right);
}
//将当前层的节点集合添加进最终的结果集合
res.add(list);
}
//返回答案
return res;
}
}
107. 二叉树的层序遍历||
题目描述:
思路分析:
这道题和它的母体的做法是一样的,只不过是要将最后返回的结果以逆序返回,逆序这个东西通过栈我们就能做到。
代码如下:
/**
* 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<List<Integer>> levelOrderBottom(TreeNode root) {
//这个题其实还是按老方法做
//只不过每一回的结果用个栈结构装起来返回就行了
//创建结果集合
List<List<Integer>> res = new ArrayList<>();
//判定是否为空树
if(root == null) return res;
//创建我们需要的栈结构
Stack<List<Integer>> stack = new Stack<>();
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//先往队列中放入一个根节点
queue.add(root);
//循环处理,当队列不为空时循环进行
while(queue.isEmpty() != true){
//创建当前层的结果集合
ArrayList<Integer> list = new ArrayList<>();
//len用来记录当前层的节点个数(即队列长度),待会儿循环要用
int len = queue.size();
//循环处理遍历添加当前层各个节点进list结果结合中
for(int i=len; i>0; i--){
//创建temp暂时记录队头
TreeNode temp = queue.poll();
//将当前节点的值加入当前层结果集合中
list.add(temp.val);
//往队列中添加当前节点的下一层节点
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
//每通过一次循环,则完成一层的遍历
//因为要自底向上进行层序遍历
//那么将当前层的遍历结果集添加到栈中即可
stack.push(list);
}
//通过上述处理,现在只要将栈中的数据装倒结果集中即可
while(stack.size() > 0){
res.add(stack.pop());
}
//返回结果
return res;
}
}
199. 二叉树的右视图
题目描述:
思路分析:
没啥好分析的,变种题,很简单,看代码注释就能懂。
代码如下:
/**
* 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<Integer> rightSideView(TreeNode root) {
//还是层序遍历的变种
//我们要做的依然就是用队列进行层序遍历
//然后返回每一层的最后一个节点即可
//都是一样的内容,就不再写注释了
//创建结果集合
List<Integer> res = new ArrayList<>();
if(root == null) return res;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(queue.isEmpty() != true){
List<Integer> list = new ArrayList<>();
int len = queue.size();
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
//拿到每一层的最后一个(即最右边的元素的值放入结果集合中)
//List集合中的indexOf()不返回值,get()才返回对应的值
res.add(list.get(list.size()-1));
}
return res;
}
}
637. 二叉树的层平均值
题目描述:
思路分析:
一样的做法,就是多了一步对每层的节点进行求平均值的操作而已,注意类型,int精度会不够,用double类型。
代码如下:
/**
* 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<Double> averageOfLevels(TreeNode root) {
//依然是层序遍历的变种题目
//求出每一层的节点集合,做一个求平均值的操作即可
//创建结果集合
List<Double> res = new ArrayList<>();
//创建一个队列
LinkedList<TreeNode> queue = new LinkedList<>();
//先往队列中添加根节点
queue.add(root);
//循环处理树的每一层
while(!queue.isEmpty()){
//当前层的结果集合
LinkedList<Integer> list = new LinkedList<>();
//用len来记录当前层长度,等会儿用来做循环变量
int len = queue.size();
//将当前层的所有节点加入层结果集合中
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
//现在求出每一层的平均值将其加入最终的结果集合中res
double sum = 0; //sum累加器
while(list.size()>0){
sum += list.remove();
}
//ans求当前层的平均值
double ans = sum / len;
//将结果添加进结果集
res.add(ans);
}
//返回结果
return res;
}
}
429. N叉树的层序遍历
题目描述:
思路分析:
换汤不换药,其实就是把原来的左右节点给换成一个叫children的子节点集合而已,我们把原来的添加左右子节点给换成集合中的所有节点就可以了。代码中有详细注释。
代码如下:
/*
// 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 List<List<Integer>> levelOrder(Node root) {
//创建结果集合
List<List<Integer>> res = new ArrayList<>();
//树空,直接返回
if(root == null) return res;
//创建队列
LinkedList<Node> queue = new LinkedList<>();
//向队列中加入根节点
queue.add(root);
//循环处理每层节点
while(queue.isEmpty() != true){
//创建结果集合
ArrayList<Integer> list = new ArrayList<>();
//记录当前层的长度,一会儿循环要用
int len = queue.size();
for(int i=len; i>0; i--){
Node temp = queue.poll();
list.add(temp.val);
//判断有没有下一层节点,有则加入
//因为children子节点是个集合,所以应该遍历其各个节点加入队列中
if(temp.children != null){
for(int j=0; j<temp.children.size(); j++){
queue.add(temp.children.get(j));
}
}
}
//将层结果集加入最终的结果集合中
res.add(list);
}
//返回结果
return res;
}
}
515. 在每个树行中找最大值
题目描述:
思路分析:
就是把树层序遍历之后然后在每行中进行最大值的查找,找到之后放入结果集中即可。
代码如下:
/**
* 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<Integer> largestValues(TreeNode root) {
//创建结果集合
List<Integer> res = new ArrayList<>();
//树为空,直接返回
if(root == null) return res;
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//往队列中添加树的根节点
queue.add(root);
//循环处理每层的节点
while(queue.isEmpty() != true){
//创建每层的结果集合
ArrayList<Integer> list = new ArrayList<>();
//用一个len变量来记录每层长度,待会儿遍历要用
int len = queue.size();
//循环取当前层的各个节点添加进层集合中
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
//在当前层集合中查找最大值
int maxNodeVal = list.get(0);
for(int i=1; i<list.size(); i++){
if(maxNodeVal < list.get(i)){
maxNodeVal = list.get(i);
}
}
//将最大值添加进最终的结果集合中
res.add(maxNodeVal);
}
//返回结果
return res;
}
}
116. 填充每个节点的下一个右侧节点指针
题目描述:
思路分析:
还是老方法,只要按层序遍历拿到每一层的数据,然后对每一层的数据进行单独的赋值操作即可。
代码如下:
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
//层序遍历,到每一层中的节点之后
//就将其next指针指向右侧的节点即可
//树为空,直接返回
if(root == null) return root;
//创建队列
LinkedList<Node> queue = new LinkedList<>();
//将根节点放入队列中
queue.add(root);
//循环处理
while(queue.isEmpty() != true){
//创建当前层的结果集
ArrayList<Node> list = new ArrayList<>();
//拿到树的每层长度
int len = queue.size();
//循环处理每一层的情况
for(int i=len; i>0; i--){
//取出队头
Node temp = queue.poll();
//将其添加进当前层结果集中
list.add(temp);
//将该节点的左右子节点加入队列中
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
//此时list中的数据为每一层的所有节点
//我们现在要做的就是遍历list中的节点
//将其中每个节点的next指针都指向list中的下一个节点即可
for(int i=0; i<list.size()-1; i++){
list.get(i).next = list.get(i+1);
}
//最后一个节点因为没有下一个右侧节点了
//因为题目是默认就为null,所以不需要特殊处理
}
//通过上述处理之后,可以直接返回树的根节点即可
return root;
}
}
117. 填充每个节点的下一个节点 ||
题目描述:
思路分析:
其实这道题和它的第一代题目都一样,直接照抄也是可以过的,但是那样就失去了这道题的意义了。我们完全可以不用队列来完成这题,上图中没有截全题目的意思,题目有一部分说明如下:
图中画红线部分说明了题目给的数据(即各个节点之间)其实是已经通过next指针连接起来了的。这一点对这道题的解答很重要。现在来说思路,
核心就是我们将该树的每一层都看做是一条单独的链表来进行遍历即可。
我们可以画图来分析一下这个事情:
首先我们需要三个节点,upNode和dummy还有temp。
upNode表示上一层上的节点,我们要依托上一层节点来进行下一层的遍历,结合代码会更好理解;而我们的哑头节点dummy代表每一层的起始位置;而temp则是我们的工作指针,它是用来代替我们的哑头节点dummy来进行每一层的类似于链表的遍历,以下图树为例,初始状态upNode指向1节点,那么按照之前的定义,dummy和temp则都指向2节点:
然后temp代替dummy指针向右遍历,dummy不变:
当temp遍历到空时,upNode就往下下一层,dummy和temp也往下一层:
然后继续重复上面的过程即可。
思路就是这样,但有一说一,代码实现起来还是蛮难的,我是看了题解自己分析出来的,代码只能说多看然后结合上面的过程理解性的记忆叭。
代码如下:
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
//dummy哑节点,永远指向下一层的起始位置
Node dummy = new Node();
//upNode初始时指向上一层的起始位置,然后通过upNode遍历上一层所以节点
Node upNode = root;
//第一层循环,控制从上一层到下一层,遍历所有层级
//因为题目条件中给了该树序列化的结构是用next指针来连接各个节点的
//所以控制跳层的循环条件也用upNode!=null
while(upNode != null){
//temp工作指针,代替dummy来完成题目要求(即next指向右边节点)
Node temp = dummy;
//第二层循环,循环遍历上一层
//通过上一层可以使得下一层完成题目的要求
while(upNode != null){
//上一层的一个节点的左子节点不为空
//那么temp就指向该左子节点,然后temp往右窜
if(upNode.left != null){
temp.next = upNode.left;
temp = temp.next;
}
//右子节点同理,正好temp就能连完一层
if(upNode.right != null){
temp.next = upNode.right;
temp = temp.next;
}
//upNode后移,指向上层中的下一个节点
upNode = upNode.next;
}
//当跳出上面那个循环时,表示上一层已经遍历完
//而temp已经将dummy的next指向了下一个节点
//所以dummy.next是指向的下一层的起始位置
//那么让upNode指向下一层
upNode = dummy.next;
//断开dummy与上一层的连接,使之重新成为下一层的起始节点(这在下一此循环时temp会为其做到)
dummy.next = null;
}
return root;
}
}
104. 二叉树的最大深度
思路分析:
哎…差不多十个题都是这种套路,把所有层的数量统计起来,不就是最大深度了嘛。
代码如下:
/**
* 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) {
//还不就是二叉树的层序遍历么...
//那个计数器记录一下有多少层不就是最大深度?
//easy!
//空树的话,返回0
if(root == null) return 0;
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//depth计数器,记录深度
int depth = 0;
//往队列中添加根节点
queue.add(root);
//循环处理
while(queue.isEmpty() != true){
//每进入一层,深度+1
depth++;
//拿到当前层的节点个数
int len = queue.size();
//循环添加下一层的所有节点
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
}
//返回结果
return depth;
}
}
111. 二叉树的最小深度
题目描述:
思路分析:
依然是二叉树的层序遍历
因为我们在层序遍历时往队列中加节点是一个一个从左往右加的
所以一旦当遇到哪一个节点的左右节点都为空时
那么该节点所处的层级就绝逼是这颗树的最小深度了
代码如下:
/**
* 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 minDepth(TreeNode root) {
//依然是二叉树的层序遍历
//因为我们在层序遍历时往队列中加节点是一个一个从左往右加的
//所以一旦当遇到哪一个节点的左右节点都为空时
//那么该节点所处的层级就绝逼是这颗树的最小深度了
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//判断是否为空树,空树则返回0,就是一层都没有
if(root == null) return 0;
//计数器,用来记录深度(也就是树的层数)
//不为空树的话则至少深度为1(即树根)
int depth = 0;
//往队列中添加树根
queue.add(root);
//循环处理
while(queue.isEmpty() != true){
//每进入一层,深度+1
depth++;
//计数器,记录当前层的节点个数
int len = queue.size();
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
//如果当前节点的左右节点都为空,那么这一层肯定是最小深度
if(temp.left == null && temp.right == null){
return depth; //那么直接返回深度depth即可
}
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
}
return depth;
}
}
226. 翻转二叉树
题目描述:
思路分析:
这道题蛮简单的,我们依然可以用层序遍历来做,就是层序遍历中对每一个节点先做其左右子节点的交换,然后再进行添加下一轮节点,将这个过程循环即可。
代码如下:
/**
* 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 invertTree(TreeNode root) {
//层序遍历
//遍历到每一个节点就将其左右子节点给交换就行
//如果树为空,直接返回null
if(root == null) return null;
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//添加第一个节点
queue.add(root);
//当队列不为空,继续循环处理各个节点
while(queue.isEmpty() != true){
//拿到当前层长度
int len = queue.size();
//对每一层循环处理
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
//将其左右子节点进行交换
TreeNode node = temp.left;
temp.left = temp.right;
temp.right = node;
//交换完成后再将左右节点放入队列以便对下一层进行操作
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
}
//返回根节点
return root;
}
}
101. 对称二叉树
题目描述:
思路分析:
看卡尔大佬的思路叭,我说不清楚,但是一般情况下,据说递归可以解决二叉树百分之九十五以上的问题。我代码中注释还是很清楚的,这个只能之际悟。
代码如下:
/**
* 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;
//否则递归调用函数compare
return compare(root.left,root.right);
}
//compare方法
public boolean compare(TreeNode left,TreeNode right){
//判断左右节点是否为空
//如果左节点为空右节点不为空,则肯定不对称
if(left == null && right != 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 outSide = compare(left.left,right.right);//即外侧对称
boolean inSide = compare(left.right,right.left);//左侧对称
//判断左右是否同时对称
boolean isSame = outSide && inSide;
//返回结果
return isSame;
}
}
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) {
//换汤不换药
//创建队列
//树为空,直接返回0
if(root == null) return 0;
LinkedList<Node> queue = new LinkedList<>();
//创建最大深度depth
int depth = 0;
//向对列中加入树根
queue.add(root);
//循环处理
while(queue.isEmpty() != true){
//n叉树深度+1,每一层循环都是一层
depth++;
//拿到当前层长度
int len = queue.size();
for(int i=len; i>0; i--){
Node temp = queue.poll();
//添加子节点,因为是个List集合
//所以我们要遍历添加
//遍历之前要先判断children是否为空
if(temp.children != null){
for(int j=0; j<temp.children.size(); j++){
queue.add(temp.children.get(j));
}
}
}
}
//返回深度depth
return depth;
}
}
222. 完全二叉树的节点个数
题目描述:
思路分析:
老方法了,暴力破解,层序遍历累加节点个数。
暴力破解代码如下:
/**
* 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 countNodes(TreeNode root) {
//先用暴力解决一下叭
//老方法,层序遍历然后用一个计数器计数节点个数
//创建队列
LinkedList<TreeNode> queue = new LinkedList<>();
//创建计数器numsOfNodes
int numOfNodes = 0;
//树为空,返回0
if(root == null) return 0;
//将树根放入队列中
queue.add(root);
//循环处理
while(queue.isEmpty() != true){
//取得当前层长度,也就是节点个数
int len = queue.size();
//每一层节点个数累加
numOfNodes += len;
//循环处理每一层
for(int i=len; i>0; i--){
TreeNode temp = queue.poll();
if(temp.left != null) queue.add(temp.left);
if(temp.right != null) queue.add(temp.right);
}
}
//返回结果
return numOfNodes;
}
}
也可以用递归来做。
递归代码如下:
/**
* 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 countNodes(TreeNode root) {
//如果当前节点为空,返回0
if(root == null) return 0;
//拿到左树的节点数
int leftNodes = countNodes(root.left);
//拿到右树的节点数
int rightNodes = countNodes(root.right);
//向上一个节点返回当前节点下左右两树的总节点数
//还要+1是因为当前节点不为空,所以至少有1啊
return leftNodes + rightNodes + 1;
}
}
110. 平衡二叉树
题目描述:
思路分析:
救命,这个自己去搜左程云的算法课看叭,有点难度的。
代码如下:
/**
* 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) {
//直接返回Info类型实例对象的isBalanced属性即可
return fun(root).isBalanced;
}
//递归函数
public Info fun(TreeNode root){
//如果当前节点为空,直接返回true
if(root == null) return new Info(true,0);
//递归调用
Info isLeft = fun(root.left);
Info isRight = fun(root.right);
//拿到树高,为什么是拿最大的那个+1呢?
//因为我们求的是高度差,那么高度肯定就是最大树深
//+1是因为根节点也算树高的一部分啊
int height = Math.max(isLeft.height,isRight.height) + 1;
//如果左树不满足条件或者右树不满足条件或者
//左树高度减去右树高度差>1时
//说明不平衡,则返回信息中的isBalanced设置为false
//否则返回true
boolean isBalanced = true;
if(isLeft.isBalanced == false || isRight.isBalanced == false || Math.abs(isLeft.height-isRight.height) > 1){
isBalanced = false;
}
return new Info(isBalanced,height);
}
}
//统一返回结果集
class Info{
boolean isBalanced; //信息之一,是否平衡
int height; //信息之二,树高多少
//构造函数初始化
public Info(boolean isBalanced,int height){
this.isBalanced = isBalanced;
this.height = height;
}
}
257. 二叉树的所有路径
题目描述:
思路分析:
挖槽,这个题目用到了递归当中的回溯部分,自己想的话感觉很难想出这样的算法…看了题解才发现十分精妙,重在理解然后多看多练叭,递归得自己多思考才行。代码再结合上代码随想录中的关于回溯的图解以及详细的思路分析,应该可以懂的。我代码中注释也写的很详细。
代码如下:
/**
* 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<>();
//判断树为空,则直接返回null
if(root == null) return res;
//创建路径集合
List<Integer> paths = new ArrayList<>();
//调用函数
traversal(root,paths,res);
//返回结果
return res;
}
//traversal函数
public void traversal(TreeNode root,List<Integer> paths, List<String> res){
//将当前节点的路径值放进去路径集合中
paths.add(root.val);
//判断是否为叶子节点
//如果当前节点的左右节点都为null,说明为叶子节点
if(root.left == null && root.right == null){
//那么将paths集合中记录的路径值依次转化为答案放入res中
//用StringBuilder来操作字符串
StringBuilder sb = new StringBuilder();
//循环取出paths集合中的值
//遍历到倒数第二个值,退出循环后再处理最后一个值
//以免发生多一个->的情况
for(int i=0; i<paths.size()-1; i++){
sb.append(paths.get(i));
//追加->符号
sb.append("->");
}
//将最后一个值添加进去
sb.append(paths.get(paths.size()-1));
//添加进最终结果集
res.add(sb.toString());
//终止本次函数
return;
}
//如果不是叶子节点
//向左递归
if(root.left != null) {
//左递归
traversal(root.left,paths,res);
//回溯,移除最后一个节点值
paths.remove(paths.size()-1);
}
//再向右递归
if(root.right != null){
traversal(root.right,paths,res);
//回溯,移除最后一个节点值
paths.remove(paths.size()-1);
}
}
}
100. 相同的树
题目描述:
思路分析:
和那个判断对称树是一类题,细微差别而已。
代码如下:
/**
* 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 isSameTree(TreeNode p, TreeNode q) {
return compare(p,q);
}
//递归函数
public boolean compare(TreeNode p,TreeNode q){
//一个为空,另一个不为空,返回false
if(p == null && q != null) return false;
else if(p != null && q == null) return false;
//两个都为空,返回true
else if(p == null && q == null) return true;
//两个都不为空,那么比较值是否相等,不相等返回false
else if(p.val != q.val) return false;
//只有两个节点的值相等的情况下才能够继续往下递归遍历
//递归调用下一层函数
//先比较左节点
boolean pqLeft = compare(p.left,q.left);
//再比较右节点
boolean pqRight = compare(p.right,q.right);
//只有当pq的左右节点都相同时才向上一层返回true
//不相等时向上返回false
return pqLeft && pqRight;
}
}
572. 另一棵树的子树
题目描述:
思路分析:
和上面的LeetCode100差不多稍微变化一下,就是在递归判断两个树是否相同的同时,也要递归的判断subRoot可能出现在root的任意位置,即递归遍历root母树的所有位置(因为root和subRoot是包含关系,所以我们需要遍历root母树进行subRoot的查找)。
代码如下:
/**
* 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 {
/*
两次递归,一次递归调用自己(isSubtree)
另外一次递归调用compare函数
也就是嵌套递归
*/
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
//如果root母树都遍历完了也还没有匹配到
//那么直接返回false
if(root == null) return false;
//否则就将从母树的头节点、左节点和右节点分别向下寻找
//只要有一条路径找到子树,都可返回true
return compare(root,subRoot) || isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot);
}
//递归函数
public boolean compare(TreeNode root,TreeNode subRoot){
//进来就判断,因为我们之前调用递归函数之前并未判断节点是否为空的情况
//如果二者有一个为空另一个不为空,那么就肯定不相同,就直接返回false
if(root == null && subRoot != null) return false;
else if(root != null && subRoot == null) return false;
//如果二者都为空,好办,说明相等,直接返回true
else if(root == null && subRoot == null) return true;
//那么现在还剩二者都不为空
//就判断它们值是否相等,如果值不相同,说明并非子树,返回false
else if(root.val != subRoot.val) return false;
//现在还剩值相同的情况,符合题意,反正确定相同了
//那么我们就递归调用即可,看其他节点是否相同
//先向左递归,比较左边节点是否相同
boolean isLeft = compare(root.left,subRoot.left);
//再右递归,比较右边节点是否相同
boolean isRight = compare(root.right,subRoot.right);
//直接返回答案,其含义是
//当当前节点的左右节点都相同时,返回true
//否则返回false
return isLeft && isRight;
}
}
113. 路径总和 ||
题目描述:
思路分析:
思路和这道题的母题是一样的,这里不再赘述,说一下自己写的时候遇到的一些坑。首先遇到这种求节点路径的,一定要想到用一个集合来记录。然后递归函数中一开始一定要往paths路径集合中添加当前节点的值,不然肯定会错,然后要注意一个点,当往一个集合中存放另一个集合时,直接采用下面的方法会方便一些:
//为目标值,加入结果集合
res.add(new ArrayList<>(paths));
就是直接new一个然后直接将我们需要转化的集合放进去即可。
/**
* 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<List<Integer>> pathSum(TreeNode root, int targetSum) {
//创建结果集合
List<List<Integer>> res = new ArrayList<>();
//如果树为空则直接返回
if(root == null) return res;
//创建路径集合
List<Integer> paths = new ArrayList<>();
//然后递归调用fun函数接口
/*
四个参数,一个是所要遍历的树,一个是目标和的值
还有res结果集合的引用和paths路径集合
*/
fun(root,targetSum,res,paths);
//返回结果
return res;
}
//调用递归函数
//因为这个题返回的是res引用即可,所以我们不需要递归函数有返回值
public void fun(TreeNode root,int targetSum,List<List<Integer>> res,List<Integer> paths){
//因为是从头节点开始操作,所以我们采用先序遍历
//将当前节点的值添加进paths路径集合中(千万别忘!!!)
paths.add(root.val);
//判断是否为子节点
//如果当前节点的左右节点均为空,说明当前节点是叶子节点
//那么我们就进行加入结果集合res的操作
if(root.left == null && root.right == null){
//判断当前路径和是否为目标值targetSum
int sum = 0; //累加器
//遍历paths集合取和
for(int i=0; i<paths.size(); i++){
sum += paths.get(i);
}
//判断是否为目标值
if(sum == targetSum){
//为目标值,加入结果集合
res.add(new ArrayList<>(paths));
}
//如果到了叶子节点,那么不管当前路径集合的和符不符合条件
//都将终止本次函数
return;
}
//非叶子节点情况
//先向左递归遍历
if(root.left != null){
//递归调用
fun(root.left,targetSum,res,paths);
//回溯到上一个节点,将paths路径集合的值删除最后一个
paths.remove(paths.size() - 1);
}
//再向右递归
if(root.right != null){
//递归调用
fun(root.right,targetSum,res,paths);
//回溯到上一个节点,将paths路径集合的值删除最后一个
paths.remove(paths.size() - 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 {
public TreeNode buildTree(int[] inorder, int[] postorder) {
//因为不管中序还是后续遍历,遍历的都是一颗二叉树
//所以两个数组的大小应该是一样的
//如果有一个数组的大小为0,那说明就是空树
//直接返回null即可
if(inorder.length == 0 || postorder.length == 0) return null;
//树不为空,递归构建该树(左闭右开原则)
return build(inorder,postorder);
}
//递归函数
public TreeNode build(int[] inorder,int[] postorder){
//如果被分割的数组都没有元素了,那么直接返回null就行了
//这条语句不加则会数组越界
if(inorder.length == 0) return null;
//先找后续遍历的最后一个值,因为该值一定是该树的根节点
int rootVal = postorder[postorder.length - 1];
TreeNode root = new TreeNode(rootVal);
//如果当前节点是被分割的数组中最后一个值的话
//那么说明其为叶子节点,不会有左右孩子,所以直接返回该树即可
if(postorder.length == 1) return root;
//根据从后续遍历中找到的根节点,注意题意无重复值
//那么我们通过该根节点从中序遍历中找到该值后
//将中序遍历一分两半
int rootIndex = 0; //rootIndex为该树根节点在中序遍历中的位置索引
for(int i=0; i<inorder.length; i++){
//找到树根节点在中序数组中的位置索引了
//直接退出
if(root.val == inorder[i]){
rootIndex = i;
break;
}
}
//现在就递归调用本函数,对从中序数组中进行切割的左右片区进行分割
//得到的返回值就挂到树上即可
//因为是中序遍历,所以中序数组的左侧区间的值就是左树节点
int[] inorderLeft = Arrays.copyOfRange(inorder,0,rootIndex);
//因为是中序遍历,所以中序数组的左侧区间的值就是左树节点
int[] inorderRight = Arrays.copyOfRange(inorder,rootIndex+1,inorder.length);
//后序数组要删除刚刚取出来的最后一个值
postorder = Arrays.copyOf(postorder,postorder.length-1);
//切割后序数组
int[] postorderLeft = Arrays.copyOfRange(postorder,0,inorderLeft.length);
int[] postorderRight = Arrays.copyOfRange(postorder,inorderLeft.length,postorder.length);
//递归调用函数
root.left = build(inorderLeft,postorderLeft);
root.right = build(inorderRight,postorderRight);
//返回根节点
return root;
}
}
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 {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//采用如下方式进行递归,可以免去对数组进行截取的操作
return helper(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
//递归函数
public TreeNode helper(int[] preorder,int preLeft,int preRight,int[] inorder,int inLeft,int inRight){
//递归终止的条件
//当数组越界时,自然就是终止的时候了
if(inLeft > inRight || preLeft > preRight) return null;
//val 为前序遍历第一个节点的值,即根节点的值
//idx 为根据根节点的值来找中序遍历的下标
int idx = inLeft,val = preorder[preLeft];
//创建对应的树根节点的值
TreeNode root = new TreeNode(val);
//循环查找该根节点于中序序列中的位置
for(int i=inLeft; i<=inRight; i++){
if(inorder[i] == val){
idx = i;
break;
}
}
//现在根据 idx 来递归查找右子树
root.left = helper(preorder,preLeft+1,preLeft+(idx-inLeft),inorder,inLeft,idx-1);
root.right = helper(preorder,preLeft+(idx-inLeft)+1,preRight,inorder,idx + 1,inRight);
//返回结果
return root;
}
}
654. 最大二叉树
题目描述:
思路分析:
我靠这个和上面的按前序和中序遍历结果来创建二叉树完全是一模一样的,甚至还要更加简单些。
代码如下:
/**
* 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) {
//这还是之前的方法
//左闭右开原则
return fun(nums,0,nums.length);
}
//递归方法
public TreeNode fun(int[] nums,int left,int right){
//递归终止条件
//当左索引下标大于等于了右索引下标
//说明没有元素了,直接返回null即可
if(left >= right) return null;
//否则执行如下
//找到当前数组中的最大值及其索引下标
int idx = left; //idx为最大值的下标索引
int max = nums[idx]; //max为数组中的最大值
for(int i=left; i<right; i++){
if(max < nums[i]){
max = nums[i];
idx = i;
}
}
//找到最大值之后创建以该节点为根节点的树
TreeNode root = new TreeNode(max);
//然后进入递归处理左子树和右子树
root.left = fun(nums,left,idx);
root.right = fun(nums,idx + 1,right);
//返回节点
return root;
}
}
617. 合并二叉树
题目描述:
思路分析:
其实二叉树递归都有套路的,写多了就会的,这个题我也是就按照之前学的套路一遍就AC了,思路都差不多。
代码如下:
/**
* 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 mergeTrees(TreeNode root1, TreeNode root2) {
//本题我们选择以root2作为根节点
//重塑一颗树返回结果
return fun(root1,root2);
}
//递归函数
public TreeNode fun(TreeNode root1, TreeNode root2){
//如果两个树根都为空,则直接返回null
if(root1 == null && root2 == null) return null;
//如果一空一不空
//如果root1不为空则将root1赋给root2
else if(root1 != null && root2 == null){
root2 = root1;
return root2;
//如果root1为空,root2不为空,则直接返回root2即可
}else if(root1 == null && root2 != null){
return root2;
}
//现在就剩root1和root都不为空的情况了
//按题目要求,将重叠的两个点香相加赋值给root2节点的值即可
root2.val = root1.val + root2.val;
//递归处理结果树的左右节点
root2.left = fun(root1.left,root2.left);
root2.right = fun(root1.right,root2.right);
//返回结果
return root2;
}
}
700. 二叉搜索树中的搜索
题目描述:
思路分析:
这个其实递归做起来也很简单,但是一开始懵了的一点,就是如果题目是需要构建树的话那么递归后的值就赋值给当前节点的左右节点即可。如果只是需要返回一个节点作为答案的话那么就将自身作为答案return即可。
同时要记得二叉搜索树(BST)的特点:
BST的左子树都小于其根节点
BST的右子树都大于其根节点
代码如下:
/**
* 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 searchBST(TreeNode root, int val) {
//二叉树搜索树BST概念得清晰
//BST的左子树都小于其根节点
//BST的右子树都大于其根节点
return fun(root,val);
}
//递归函数
public TreeNode fun(TreeNode root,int val){
//递归终止条件
//如果当前树为空,则直接返回null
if(root == null) return null;
//如果root.val与目标值val相等了,则就是我们要找的子树的根节点
//直接返回即可
else if(root.val == val) return root;
//因为是二叉搜索树(BST)
//所以递归时我们可以根据其特性来进行单边搜索
//如果当前节点的值大于目标值时,我们往左树递归
if(root.val > val) return fun(root.left,val);
//如果当前节点的值小于目标值,我们往右树递归
else return fun(root.right,val);
}
}
97. 验证二叉搜索树
题目描述:
思路分析:
暴力方法,因为二叉搜索树的特性:左节点的值一定小于中间节点的值,且右节点的值一定大于中间节点的值。那么我们对一颗BST进行中序遍历时,得到的遍历结果一定是按升序排列的,反之如果不是升序的则一定不是二叉树。所以我们只需要用一个集合来装这个遍历结果,然后对该集合进行遍历判断是否为升序就可以拿到答案。
代码如下:
/**
* 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 isValidBST(TreeNode root) {
//判断是否是一颗二叉搜索树
//我们只要看在中序遍历下,其输出顺序是否有序即可
//创建结果集合
List<Integer> list = new ArrayList<>();
//判空
if(root == null) return false;
//调用fun函数拿到这个集合
fun(root,list);
//然后对list集合进行循环判断,如果遍历是非升序情况
//则不是二叉搜索树,返回false
for(int i=1; i<list.size(); i++){
//等于情况是排除的重复情况,重复也不是BST
//因为BST的定义必须小于或者大于
if(list.get(i) <= list.get(i-1)) return false;
}
//否则就是二叉搜索树
return true;
}
//递归函数,按中序遍历该二叉树
public void fun(TreeNode root,List list){
//先进行左递归
if(root.left != null) fun(root.left,list);
//将结果加入结果集合中
list.add(root.val);
//然后进行右递归
if(root.right != null) fun(root.right,list);
}
}
530. 二叉搜索树的最小绝对值
题目描述:
思路分析:
离谱,菜鸡如我只能暴力破解,因为二叉搜索树的特性中序遍历都是升序的,所以根据这个特性是可以解决很多问题的,本题也一样,看代码都明白。
代码如下:
/**
* 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 getMinimumDifference(TreeNode root) {
//创建一个结果集合,然后在这个结果集合里面找最小差值即可
List<Integer> list = new ArrayList<>();
//不用判空,题目说了至少两个节点
//递归进行集合填充
fun(root,list);
//现在来找最小差值
int minNum = list.get(1) - list.get(0);
for(int i=1; i<list.size(); i++){
if(minNum > list.get(i)-list.get(i-1)){
minNum = list.get(i)-list.get(i-1);
}
}
return minNum;
}
//递归函数
public void fun(TreeNode root,List list){
//左递归
if(root.left != null) fun(root.left,list);
//将结果添加进结果集合
if(root != null) list.add(root.val);
//右递归
if(root.right != null) fun(root.right,list);
}
}
501. 二叉搜索树中的众数
题目描述:
思路分析:
具体看卡尔哥的代码随想录中的利用二叉搜索树特性来解决本题的解法,我觉得还是很清晰易懂的。我注释中也都写的很清楚。
代码如下:
/**
* 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 {
//利用二叉搜索树的特性来做
//下面四个变量声明为全局变量以方便操作
ArrayList<Integer> resList;//结果数组
int maxCount; //最大频率
int count; //频率
TreeNode pre;//记录上一个节点的工作指针
public int[] findMode(TreeNode root) {
//初始化我们的全局变量
resList = new ArrayList<>();
maxCount = 0;
count = 0;
pre = null;
fun(root);
//对结果集进行操作返回标准答案
int[] res = new int[resList.size()];
for(int i=0; i<resList.size(); i++){
res[i] = resList.get(i);
}
//返回数组
return res;
}
//中序遍历
public void fun(TreeNode root){
//左递归
if(root.left != null) fun(root.left);
if(root != null){
int rootVal = root.val;//拿到这个当前节点的值
//计数
//如果pre==null,说明是第一个节点,该数出现第一次那么频率为1
//或者如果当前节点的值与上一节点的值不同,那么是另一值第一次出现
//也设置为1
if(pre == null || rootVal != pre.val){
count = 1;
}else{//不然的话,就不是第一次出现了,那么频率+1
count++;
}
//更新结果以及最大频率值maxCount
if(count > maxCount){//如果当前频率值大于了最大频率值
resList.clear();//那么我们清空之前结果集合里存的频率值
//加入我们现在新的最大频率值对应的节点值
resList.add(rootVal);
//最大频率值现在也要更新成当前的频率值
maxCount = count;
}else if(count == maxCount){
//频率值与最大频率值相同那么加入结果集中
resList.add(rootVal);
}
//更新工作指针pre指向当前节点
//这样下一节点中的pre指向的就是上一节点
pre = root;
}
//右递归
if(root.right != null) fun(root.right);
}
}