目录
- 二叉树的前中后序遍历
- 递归方法
- 迭代方法(未统一写法)
- ·前序迭代
- ·中序迭代
- ·后序迭代
- 迭代方法(统一模板)
- 二叉树遍历
- LeetCode 144. 二叉树的前序遍历
- LeetCode 145. 二叉树的后序遍历
- LeetCode 94. 二叉树的中序遍历
- n叉树的遍历
- LeetCode 589. N 叉树的前序遍历
- LeetCode 590. N 叉树的后序遍历
- 二叉树的层序遍历
- 力扣 102.二叉树层序遍历
- 力扣 107.二叉树的层序遍历II
- 力扣 104. 二叉树的最大深度
- 力扣 111. 二叉树的最小深度
- 力扣 199. 二叉树的右视图
- 力扣 637. 二叉树的层平均值
- 力扣 429. N 叉树的层序遍历
- 力扣 515. 在每个树行中找最大值
- 力扣 116. 填充每个节点的下一个右侧节点指针
- 力扣 117. 填充每个节点的下一个右侧节点指针 II
- 二叉树深度问题
- 力扣 104. 二叉树的最大深度
- 力扣 559. N 叉树的最大深度
- 力扣 111. 二叉树的最小深度
二叉树的前中后序遍历
递归方法
//前序遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<Integer>();//用res来存遍历结果
preOrder(res,root);
return res;
}
public void preOrder(List<Integer> res,TreeNode root){
if(root==null)return;//递归终止条件,遍历到空节点就返回上一层
res.add(root.val);//访问结点(这句位置决定递归顺序)
preOrder(res,root.left);
preOrder(res,root.right);
}
}
//中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
inOrder(res,root);
return res;
}
public void inOrder(List<Integer> res,TreeNode root){
if(root==null)return;
inOrder(res,root.left);
res.add(root.val);
inOrder(res,root.right);
}
}
//后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new LinkedList<>();
postOrder(res,root);
return res;
}
public void postOrder(List<Integer> res,TreeNode root){
if(root==null)return;
postOrder(res,root.left);
postOrder(res,root.right);
res.add(root.val);
}
}
迭代方法(未统一写法)
因为递归调用会把函数的局部变量、参数值和返回的地址入栈,return的时候从栈顶弹出上次的各项参数,所以递归可以返回上一层位置(实质是用栈记录下了上一层的信息)。所以三种遍历方式可以用栈来模拟递归的过程,也就是所谓的迭代(递归是一环套一环的执行,而迭代是按照一定的逻辑顺序向前推进)。
·前序迭代
前序遍历的顺序是根->左->右,需要先处理根结点(根节点先入栈,在后续每一次处理中,都将栈顶元素出栈,加入res中),然后将右孩子入栈,再将左孩子入栈,这样可以保证出栈的顺序是左孩子在右孩子前;
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res= new LinkedList<>();
Stack<TreeNode> stack=new Stack<>();//栈里面存的是TreeNode类型的结点
if(root!=null)stack.push(root);//如果空树,什么都不管,栈就是空的,最后直接返回空的res
//前序遍历顺序(根左右),用栈模拟要根先入栈再出栈,然后右结点入栈,左节点入栈,这样出栈顺序就符合
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
if(root.right!=null)stack.push(root.right);
if(root.left!=null)stack.push(root.left);
}
return res;
}
}
·中序迭代
做完了前序迭代,肯定是想稍微改改前序迭代代码就套上中序,但这样是不行的。根源就在于对根结点的访问和处理上,前序的顺序是中左右,遍历过程中最先访问(遍历)的是根结点,每次循环中最先处理(出栈加入res)的也都是中间节点,然后是孩子结点。也就是说对结点访问和处理是连在一起的
但是中序遍历的顺序是左中右,要先访问根节点,然后一直向左子树访问,直到底部,开始处理结点(出栈加入res中),也就是说对结点的访问和处理是分开进行的,导致了处理和访问顺序的不一致。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res =new LinkedList<>();
if(root==null)return res;
Stack<TreeNode> stack =new Stack<>();
TreeNode cur=root;//用cur来遍历树
//当栈或当前遍历结点非空的时候
while(!stack.isEmpty()||cur!=null){
if(cur!=null){//当前遍历结点非空,就要入栈,然后往左子树遍历
stack.push(cur);
cur=cur.left;//左
}else{//当前遍历结点为空
cur=stack.peek();//栈顶元素就是要处理的数据,用cur保存
stack.pop();
res.add(cur.val);//中
cur=cur.right;//再往右子树遍历
}
}
return res;
}
}
·后序迭代
前序迭代顺序是中左右,后序是左右中,其实只要在前序基础上,将顺序变为中右左,再将最后的res翻转,就可以得到左右中的顺序;
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res =new LinkedList<>();
if(root==null)return res;
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
if(root.left!=null)stack.push(root.left);
if(root.right!=null)stack.push(root.right);
}
Collections.reverse(res);
return res;
}
}
迭代方法(统一模板)
在前一组迭代写法中,中序遍历是因为需要先访问到最左下才可以开始处理结点,而前序是可以一边访问一边处理,这样导致了代码写法的不一致,为了统一这个风格,可以在遍历的过程中,都将结点入栈,只不过在要处理的节点(也就是要加入res的结点)入栈后再入栈一个null结点作为标记,这样在后续出栈中如果碰到null结点,就知道下一个出栈的结点是要加入res的,这样一来只要调整左中右结点的入栈顺序,就可以控制最后出栈的顺序;说得有些绕,直接看三种遍历的对比代码找感觉:
//前序遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
Deque<TreeNode> stack=new LinkedList<>();
if(root!=null)stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.peek();//cur获取栈顶元素
if(cur!=null){//栈顶元素非空,出栈
stack.pop();//避免后续对栈顶的元素重复操作,统一先出栈(前面已经用cur记录了,不怕丢)
if(cur.right!=null)stack.push(cur.right);//若左右子树为空,则空节点不入栈,保证栈顶元素为空节点只发生在是中结点入栈后
if(cur.left!=null)stack.push(cur.left);
//因为是前序遍历(中左右),入栈要按右左中的顺序
stack.push(cur);
stack.push(null);//在中结点入栈后,null入栈作为标记;
}else{//若栈顶元素为空,说明下一个栈顶元素是“中”结点
stack.pop();//null出栈
cur=stack.peek();//null的下一个栈顶元素就是“中结点”,用cur暂存
stack.pop();//存下后就可出栈
res.add(cur.val);
}
}
return res;
}
}
//中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res= new ArrayList<>();
Deque<TreeNode> stack=new LinkedList<>();
if(root!=null)stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.peek();
if(cur!=null){
stack.pop();
if(cur.right!=null)stack.push(cur.right);
stack.push(cur);
stack.push(null);
if(cur.left!=null)stack.push(cur.left);
}
else{
stack.pop();
cur=stack.peek();
stack.pop();
res.add(cur.val);
}
}
return res;
}
}
//后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
Deque<TreeNode> stack= new LinkedList<>();
if(root!=null)stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.peek();
if(cur!=null){
stack.pop();
stack.push(cur);
stack.push(null);
if(cur.right!=null)stack.push(cur.right);
if(cur.left!=null)stack.push(cur.left);
}else{
stack.pop();
cur=stack.peek();
stack.pop();
res.add(cur.val);
}
}
return res;
}
可以观察到这样设置的话,代码的区别就只在于
//1
stack.push(cur);
stack.push(null);
以及
//2
if(cur.right!=null)stack.push(cur.right);
//3
if(cur.left!=null)stack.push(cur.left);
这三句的顺序的不同。
二叉树遍历
LeetCode 144. 二叉树的前序遍历
原题链接
2023.06.11 二刷
递归方法代码如下:
//递归版本
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
preOrder(res,root);
return res;
}
public void preOrder(List<Integer> res,TreeNode root){
if(root==null)return;
res.add(root.val);
preOrder(res,root.left);
preOrder(res,root.right);
}
}
迭代方法(非统一模板),代码如下:
//迭代版本
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res= new ArrayList<>();
Deque<TreeNode> stack=new LinkedList<>();
if(root!=null)stack.push(root);
//前序遍历顺序(根左右),用栈模拟要根先入栈再出栈,然后右结点入栈,左节点入栈,这样出栈顺序就符合
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
if(root.right!=null)stack.push(root.right);
if(root.left!=null)stack.push(root.left);
}
return res;
}
}
迭代法,统一模板:
思想:
迭代法需要使用栈,但用栈的话就无法解决遍历结点和处理结点的一致问题。
那么将访问到的结点放入栈中,处理过的结点也放进去,但是要对它进行标记,即处理的结点放入栈中之后,紧接着放入一个空指针入栈作为标记。后面栈顶元素碰到null,说明null的下一个栈顶元素就是访问过但还没处理的元素,后面就可以对null进行出栈,保存下一个栈顶元素,然后栈顶元素出栈,将保存的元素值加入res
代码如下:
//迭代统一版
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
if(root==null)return res;
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();//栈顶出栈,并让cur存储出栈元素
if(cur!=null){//出栈栈顶元素的非
// 判断出栈元素左右子树是否为空,不为空就入栈
if(cur.right!=null)stack.push(cur.right);
if(cur.left!=null)stack.push(cur.left);
//因为是前序遍历(中左右),入栈要按右左中的顺序
stack.push(cur);
stack.push(null);//在中结点入栈后,null入栈作为标记;
}else{//若栈顶元素为空,说明下一个栈顶元素是“中”结点
cur=stack.pop();;//null的下一个栈顶元素就是“中结点”,用cur暂存
res.add(cur.val);
}
}
return res;
}
}
LeetCode 145. 二叉树的后序遍历
原题链接
2023.06.11 二刷
递归版本代码如下:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new LinkedList<>();
postOrder(res,root);
return res;
}
public void postOrder(List<Integer> res,TreeNode root){
if(root==null)return;
postOrder(res,root.left);
postOrder(res,root.right);
res.add(root.val);
}
}
迭代版本,修改自前序遍历
前序遍历:中左右,调整代码入栈顺序->中右左,反转res->左右中(后续遍历)
代码如下:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res =new Arraylist<>();
if(root==null)return res;
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
if(root.left!=null)stack.push(root.left);
if(root.right!=null)stack.push(root.right);
}
Collections.reverse(res);
return res;
}
}
迭代法,统一模板,代码如下:
//统一版本
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
if(root==null)return res;
Deque<TreeNode> stack= new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
if(cur!=null){
stack.push(cur);
stack.push(null);
if(cur.right!=null)stack.push(cur.right);
if(cur.left!=null)stack.push(cur.left);
}else{
cur=stack.pop();
res.add(cur.val);
}
}
return res;
}
}
LeetCode 94. 二叉树的中序遍历
原题链接
2023.06.11 二刷
递归版本,代码如下:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
inOrder(res,root);
return res;
}
public void inOrder(List<Integer> res,TreeNode root){
if(root==null)return;
inOrder(res,root.left);
res.add(root.val);
inOrder(res,root.right);
}
}
迭代法,非统一模板
中序遍历与前序和后序遍历不太一样
前序遍历的时候,遍历结点和处理结点是一起前后进行的,而中序遍历无法这样
代码如下:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res =new LinkedList<>();
if(root==null)return res;
Stack<TreeNode> stack =new Stack<>();
TreeNode cur=root;//用cur来遍历树
//当栈或当前遍历结点非空的时候
while(!stack.isEmpty()||cur!=null){
if(cur!=null){//当前遍历结点非空,就要入栈,然后往左子树遍历
stack.push(cur);
cur=cur.left;//左
}else{//当前遍历结点为空
cur=stack.peek();//栈顶元素就是要处理的数据,用cur保存
stack.pop();
res.add(cur.val);//中
cur=cur.right;//再往右子树遍历
}
}
return res;
}
}
迭代法,统一模板,代码如下:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res= new ArrayList<>();
if(root==null)return res;
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
if(cur!=null){
if(cur.right!=null)stack.push(cur.right);
stack.push(cur);
stack.push(null);
if(cur.left!=null)stack.push(cur.left);
}else{
cur=stack.pop();
res.add(cur.val);
}
}
return res;
}
}
n叉树的遍历
LeetCode 589. N 叉树的前序遍历
原题链接
//递归法
class Solution {
List<Integer> res=new ArrayList<>();
public List<Integer> preorder(Node root) {
if(root==null)return res;
res.add(root.val);
for(Node child:root.children)preorder(child);
return res;
}
}
/*
前序遍历顺序是根-左-右,每层循环里面都是根结点先出栈,加入res,可以保证结果集中根会在孩子结点前;
还需要解决左右的顺序,栈先进后出的特性,要先让右孩子入栈,这样后面出栈可让左孩子先出栈加入res,保证结果集中的左-右顺序;结合起来就是根-左-右的顺序;
*/
//借助栈实现的迭代法
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> res=new ArrayList<>();
if(root==null)return res;
Stack<Node>stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
//因为栈先进后出,所以需要先让右边的孩子结点入栈
for(int i=root.children.size()-1;i>=0;--i)stack.push(root.children.get(i));
}
return res;
}
}
LeetCode 590. N 叉树的后序遍历
原题链接
//递归法
class Solution {
List<Integer> res=new ArrayList<>();
public List<Integer> postorder(Node root) {
if(root==null)return res;
for(Node child:root.children)postorder(child);
res.add(root.val);
return res;
}
}
/*
与前序遍历迭代改后序遍历迭代类似,前序是根-左-右,后序是左-右-根;
只要在入栈时,让左孩子先于右孩子入栈,这样出栈就是右孩子先出,保证右-左顺序加入res;
而根总是先于孩子结点入栈,在孩子结点入栈的那层循环里,根结点值已经加入res;
这样可以保证结果集里是根-右-左,只要最后翻转res就可以变成左-右-根;
*/
//后序迭代法
class Solution {
public List<Integer> postorder(Node root) {
List<Integer> res=new ArrayList<>();
if(root==null)return res;
Stack<Node>stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
root=stack.pop();
res.add(root.val);
for(int i=0;i<root.children.size();++i)
stack.push(root.children.get(i));
}
Collections.reverse(res);
return res;
}
}
二叉树的层序遍历
力扣 102.二叉树层序遍历
原题链接
2023.06.12 二刷
层序遍历的主要思想需要借助队列先进先出的特性来存储遍历到的结点的左右孩子。
具体思想是:最开始将根节点加入队列,然后遍历队列,如果队列不为空,将队头结点出队,将队头结点值加入res中,并且将当前出队的结点的左右孩子加入队列;继续遍历队列,队列不为空的时候,重复出队头、加入res、左右孩子入队的操作;这样可以保证遍历二叉树的时候,永远都是在最上层的结点先被访问到(因为先出队的才能先被访问到)。
//借助队列实现层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();//res存储结果,注意题目的结果的格式(res是由许多resItem组成);
if(root==null)return res;//空树返回空结果
Queue<TreeNode> que=new LinkedList<>();//借助队列实现层序遍历,que继承LinkedList类(可以使用里面的方法)
que.offer(root);//入队操作(队尾入队),add方法出错会抛出异常,offer只是返回布尔值,所以用offer方法
//外层的while用来遍历整个队列(输出到res中)
while(!que.isEmpty()){
int len=que.size();//len用来记录当前遍历到的这一层有多少结点
List<Integer> resItem =new ArrayList<>();//每个resItem存这一层的结点值
//内层的while用来遍历每一层的结点(输出到resItem)
while(len-->0){//遍历这一层的结点,遍历完len就到0
TreeNode cur=que.poll();//poll获取队首元素并且出队
resItem.add(cur.val);//结点值加入这一层的结果中
if(cur.left!=null)que.offer(cur.left);//左右孩子入队
if(cur.right!=null)que.offer(cur.right);
}
res.add(resItem);//一层遍历结束,这一层的结点值加入最终结果中
}
return res;
}
}
力扣 107.二叉树的层序遍历II
原题链接
这题要求返回自底向上的层次遍历结果,只要在102题的基础上把res翻转就行了,在java里面可以用Collections.reverse(res),效果就相当于输出从底层到顶层的层序结果。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
List<Integer> resItem= new ArrayList<>();
while(len-->0){
TreeNode cur=que.poll();
resItem.add(cur.val);
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(resItem);
}
Collections.reverse(res);
return res;
}
}
力扣 104. 二叉树的最大深度
原题链接
这题要求输出给定二叉树的最大层数,同样可以用层序遍历的思想解决,只要在外层while循环里面加一个depth用来记录层数即可。
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)return 0;
int depth=0;//记录当前遍历的深度
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;//外层while遍历所有层
while(len-->0){//内层while循环用来遍历这一层的所有结点
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return depth;
}
}
力扣 111. 二叉树的最小深度
原题链接
这题要求的是最小的深度,只要在104基础上加一个判断就行。在内层while循环中,若当前结点没有左右孩子,说明它是叶子结点,这里就是最小深度。
class Solution {
public int minDepth(TreeNode root) {
if(root==null)return 0;
int depth=0;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;
while(len-->0){
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
//如果当前结点无左右孩子,说明它是叶子结点,则此层有最小深度
if(cur.left==null&&cur.right==null)return depth;
}
}
return depth;//如果不会提前返回,说明会一直到最后一层
}
}
力扣 199. 二叉树的右视图
原题链接
这题要求输出每一层的最右边的结点值,只要在内层while循环里面判断是不是遍历到了这一层最后一个结点,如果是,把结点值加入res。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res =new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
TreeNode cur= que.poll();
if(len==0)res.add(cur.val);//当len=0时,说明这一层遍历到了最后一个结点
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return res;
}
}
力扣 637. 二叉树的层平均值
原题链接
这题要求输出每一层的结点值的平均值,直接在层序遍历过程中计算即可。
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res =new ArrayList<>();
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
double sum=0;//sum用于累加每一层的结点值的总和
for(int i=0;i<len;i++){//这里用while不太方便,因为后面还需要用到len的值,不好用while(len-->0)
TreeNode cur= que.poll();
sum+=cur.val;
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(sum/len);//该层遍历结束,求平均值
}
return res;
}
}
力扣 429. N 叉树的层序遍历
原题链接
要求输出n叉树的层序遍历结果,与二叉树层序思路一样。
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res =new ArrayList<>();
if(root==null)return res;;
Queue<Node> que=new LinkedList<>();//注意队列中的结点类型根据题意设置
que.offer(root);
while(!que.isEmpty()){
List<Integer> resItem=new ArrayList<>();
int len=que.size();
while(len-->0){
Node cur=que.poll();
resItem.add(cur.val);
//n叉树只要在内层while循环时,相比二叉树访问左右孩子,把自己孩子访问完就行
for(Node child:cur.children)que.offer(child);
}
res.add(resItem);
}
return res;
}
}
力扣 515. 在每个树行中找最大值
原题链接
要求找出二叉树每一层中的最大值,只要在层序遍历过程中更新每层最大值,在一层遍历结束后将最大值加入res中即可。
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> res =new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que= new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
int max=que.peek().val;//每一层遍历开始前,先把最大值设为队头结点的值
while(len-->0){
TreeNode cur=que.poll();
if(cur.val>max)max=cur.val;//如果遍历过程碰到更大值,就更新
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(max);
}
return res;
}
}
力扣 116. 填充每个节点的下一个右侧节点指针
原题链接
要求将满二叉树每一层的结点用next从左到右串联;每个结点有val、next指针、left和right指针,初始next全置null;
解法1:
不考虑空间效率,可以用层序遍历的方法,空间复杂度O(n);也可以使用递归的方法,题目中说递归的调用栈不算,也可以粗略认为使用常数级空间;
//层序遍历方法(O(n)空间复杂度)
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Queue<Node> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
Node cur=que.poll();
if(len>0)cur.next=que.peek();//len=0时,cur为该层最后一个结点(本身就指向null),不用管;所以只要处理len>0的情况就行
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return root;
}
}
//递归方法1
class Solution {
public Node connect(Node root) {
if(root==null)return root;
connectTwoNode(root.left,root.right);
return root;
}
public void connectTwoNode(Node leftNode,Node rightNode){
if(leftNode==null&&rightNode==null)return;
leftNode.next=rightNode;
connectTwoNode(leftNode.left,leftNode.right);
connectTwoNode(rightNode.left,rightNode.right);//连接右孩子的左右子结点
connectTwoNode(leftNode.right,rightNode.left);//连接左孩子的右子结点与右孩子的左子结点
}
}
//递归方法2
class Solution {
public Node connect(Node root) {
if(root==null)return null;
if(root.left!=null){
//到这里可以保证root.left不是null,就可以连接root.left与root.right
root.left.next=root.right;
//root同层有后继(右兄弟),连接root的右子结点与root右兄弟的左子结点
if(root.next!=null)root.right.next=root.next.left;
}
connect(root.left);//递归到下一层
connect(root.right);
return root;//最终返回
}
}
解法2:
想办法在常数级空间复杂度下完成next的链接,这就需要借用上一层已经建立的next指针了,也就是第N层next链接建立后,在进行第N+1层next链接的时候,会用到第N层的next域;
使用这个方法要明白满二叉树中存在两种next链接,第一种是同一父节点下的两个子结点的next链接,第二种是相邻的父节点间的子结点的链接,即第一个父节点的右孩子与第二个父节点的左孩子的链接;
//第一种
node.left.next = node.right;
//第二种
node.right.next = node.next.left
所以完整代码如下:
//常数级空间复杂度
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Node mostLeft=root;//mostLeft用于记录每层的最左结点
//用mostLeft.left遍历每一层
while(mostLeft.left!=null){
Node head =mostLeft;//head用于遍历这一层的所有结点
while(head!=null){
head.left.next=head.right;//同一个父节点的子结点之间建立链接
//不同父节点的子结点建立链接
if(head.next!=null)head.right.next=head.next.left;
head=head.next;
}
mostLeft=mostLeft.left;
}
return root;
}
}
力扣 117. 填充每个节点的下一个右侧节点指针 II
原题链接
相比于上一题,这题的二叉树形没有限制(可以不是满二叉树),在不考虑空间效率前提下,可以用上题的层序遍历的方法,代码完全一样。
**·简单省事型模板解法:**空间复杂度O(n)
//层序遍历方法
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Queue<Node> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
Node cur=que.poll();
if(len>0)cur.next=que.peek();//len=0时,cur为该层最后一个结点(本身就指向null),不用管;所以只要处理len>0的情况就行
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return root;
}
}
·常数级空间复杂度解法:空间复杂度O(1)
class Solution {
public Node connect(Node root) {
if (root == null)return root;
//cur我们可以把它看做是每一层的链表
Node cur = root;
//遍历当前层的时候,为了方便操作在下一层前面添加一个虚拟头结点(注意这里是访问当前层的节点,然后把下一层的节点串起来)
Node dummy = new Node();
while (cur != null) {
//tail表示访到的下一层节点,也就是已经串好的下一层链表的最后一个点
Node tail = dummy;
//然后开始遍历当前层的链表
while (cur != null) {
if (cur.left != null) {
//如果当前节点的左子节点不为空,就让tail节点的next指向他
tail.next = cur.left;
tail = tail.next;//然后再更新tail,实际上就是用tail来串这一层的点
}
if (cur.right != null) {
tail.next = cur.right;
tail = tail.next;
}
//cur遍历当前层,继续访问这一层的下一个节点
cur = cur.next;
}
//把下一层串联成一个链表之后,更新cur到下一层,直到cur为空为止
cur = dummy.next;
dummy.next=null;//断开dummy和这一层的联系,方便下层复用
}
return root;
}
}
二叉树深度问题
力扣 104. 二叉树的最大深度
原题链接
求一棵二叉树的最大的深度,有递归和迭代法。
//递归法
class Solution {
public int maxDepth(TreeNode root) {
//若当前结点为空,则为叶子结点,高度为0;
if(root==null)return 0;
//当前结点最大深度为左右子树最大深度+1(+1是因为要算上当前结点的高度1)
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
/*
层序遍历比较适合求深度,因为层序遍历是一层层遍历下去,只要在每层里面给depth+1就行,层序遍历模板用得较为熟练,用起来比较方便。
*/
//迭代法(层序遍历模板)
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)return 0;
Queue<TreeNode> que=new LinkedList<>();
int depth=0;//记录当前遍历的深度
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;//外层while遍历所有层
while(len-->0){//内层while循环用来遍历这一层的所有结点
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return depth;
}
}
力扣 559. N 叉树的最大深度
原题链接
像上题一样,可以用递归和迭代方法。
递归法中需要注意,不同于二叉树的求最大深度,n叉树有多个孩子结点,需要在访问当前结点的时候,遍历孩子结点,并求出下一层子结点中的最大深度,这需要使用depth记录下一层的子结点的最大深度;在最后返回值+1,也就是还需要算上当前遍历到的结点的1的高度。
//递归法
class Solution {
public int maxDepth(Node root) {
if(root==null)return 0;
int depth=0;
for(Node child:root.children){
depth=Math.max(maxDepth(child),depth);
}
return depth+1;
}
}
//迭代法(层次遍历模板)
class Solution {
public int maxDepth(Node root) {
if(root==null)return 0;
Queue<Node>que=new LinkedList<>();
que.offer(root);
int depth=0;
while(!que.isEmpty()){
int len=que.size();
depth++;
while(len-->0){
Node cur=que.poll();
for(Node child:cur.children)//多了遍历所有孩子的步骤
if(child!=null)que.offer(child);
}
}
return depth;
}
}
力扣 111. 二叉树的最小深度
原题链接
求最小深度需要注意,最小深度是从根节点到最近叶子节点的最短路径上的节点数量,如图:(为方便说明,采用代码随想录公众号的图片)
·因此,在递归的方法里,需要考虑左右子树分别为空的情况,排除这两种情况之后,还剩下左右子树都为空,和左右子树都存在的情况,则返回1+左右子树最小深度(若均非空,则可递归计算,若均为空,则相当于1+0)。这样的式子相当于省略了:
if(root.left==null&&root.right==null)return 1;
这一句的判断。
·在迭代方法里,由于是层序遍历从上往下的遍历,因此碰到的第一个叶子结点(左右子树都为空的结点)所在的那层深度就是最小深度。
递归和迭代的代码如下:
//递归法
class Solution {
public int minDepth(TreeNode root) {
if(root==null)return 0;
//左子树空,右子树不空,最小深度为根节点到右子树的叶子结点的深度
if(root.left==null&&root.right!=null)return 1+minDepth(root.right);
//右子树空,左子树不空,最小深度为根节点到左子树的叶子结点的深度
if(root.left!=null&&root.right==null)return 1+minDepth(root.left);
//排除了左右子树分别为空的情况,还剩下左右子树都为空,和左右子树都存在的情况,这两种情况都可以用这个式子计算:当前遍历到的结点的最小深度为1+左/右子树最小的深度
return 1+Math.min(minDepth(root.left),minDepth(root.right));
}
}
//迭代法(层序遍历模板)
class Solution {
public int minDepth(TreeNode root) {
if(root==null)return 0;
int depth=0;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;
while(len-->0){
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
//如果当前结点无左右孩子,说明它是叶子结点,则此层有最小深度
if(cur.left==null&&cur.right==null)return depth;
}
}
return depth;//如果不会提前返回,说明会一直到最后一层
}
}