本文题目均来自牛客网,部分思路来自《算法导论》,编程语言是Java。
后续也会继续收录和补充。供自己后期复习使用。
解决树问题的关键
树的半壁江山其实都是由树的”遍历“打下来的,而80%的遍历问题都可以由递归来解决。本篇文章收录了牛客网中一些关于树的在线编程用来解释:为什么递归如此重要。
要搞清楚递归问题,我们只用搞清楚递归的过程和递归退出的条件就可以了。举一个例子:
(1)小红要拉拢ABCD四个人;
(2)小红先拉拢A,意外地发现A说她只要BCD同意,A就同意,于是她只要拉拢BCD就好了;
(3)她先去拉拢B,又意外地发现B说只要拉拢CD,自己也就同意,于是她只要拉拢CD就可以了;
(4)她又去和C讲,又意外的发现C说只要D同意自己就同意,于是她只要拉拢D一个人就可以了;
(5)只要D同意,那么C也就会同意,然后B也同意,最后A也同意,任务完成。
拉拢的过程就是一个递归的过程,D同意就是递归退出的条件。
递归的好处就是把一个大问题一步步变成一个小问题,处理好小问题就可以了。思路简单,代码简单,是做二叉树问题的首选方法。
二叉树的前 中 后序遍历
前序遍历
前序遍历:
(1)遍历整棵树;先遍历根节点,再遍历根节点的左子树,右子树
(2)要遍历完根节点的左子树,只需要遍历完根节点左子树的左子树就可以了,要遍历完根节点的右子树,只需要遍历完根节点右子树的右子树就可以了;
(3)以此递归…
(4)当某一个节点的左子树右子树为空时,就可以返回了。
递归的过程:调用自身,遍历自身的左子树,右子树。
递归退出的条件:节点为空,说明已经到了树的叶节点。
中序后序遍历亦如此。
题目如下:
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* public TreeNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型一维数组
*/
ArrayList<Integer>nodelist=new ArrayList<>();
public int[] preorderTraversal (TreeNode root) {
//判空,如果需要遍历的树为空,则返回0
if(root==null){
return new int[0];
}
//循环讲ArrayList转化为int数组(该方法需要返回int)
ArrayList<Integer> nodelist=preOrder(root);
int length=nodelist.size();
int preorder[]=new int[length];
for(int i=0;i<length;i++){
preorder[i]=nodelist.get(i);
}
return preorder;
}
//前序遍历
ArrayList<Integer> preOrder(TreeNode root){
//递归退出的条件:节点为空
if(root==null){
return null;
}
//添加
nodelist.add(root.val);
preOrder(root.left);//递归,遍历左子树
preOrder(root.right);//递归,遍历右子树
return nodelist;
}
}
在这里我曾经犯过一个小错误,将ArrayList<Integer>nodelist=new ArrayList<>();
写在preOrder()方法内,错误的原因是每次递归调用都会新建一个ArrayList,保存的值永远只有根节点。
也可以用栈实现
层序遍历
层序遍历说白了就是一层一层的来呗,用一个辅助队列,先存储第一层节点,再存储第二层节点;从上至下,从左至右。
需要注意:因为题目要求每一层都是一个集合,所以还要注意统计每层的数量。
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类
* @return int整型ArrayList<ArrayList<>>
*/
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
ArrayList<ArrayList<Integer>>ans=new ArrayList<>();
Queue<TreeNode>queue=new LinkedList<TreeNode>();
if(root==null){
return ans;
}
queue.offer(root);
//while循环从上至下,for循环从左至右
while(!queue.isEmpty()){
//该ArrayList用于存储每一层的节点
ArrayList<Integer> list=new ArrayList<>();
//统计每层的节点个数
int length=queue.size();
for(int i=0;i<length;i++){
TreeNode node=queue.poll();
//将每一层的节点添加入list
list.add(node.val);
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
//把所有的层添加到ans
ans.add(list);
}
return ans;
}
}
'Z’字形打印二叉树
这一题和上题简单的层序遍历的区别就是奇数层和偶数层的打印顺序不一样,我们可以用两个队列来实现(偶数层将表反转)。
因为栈是先进后出,实用双栈刚好能达到本题效果。
思路:奇数层入栈1;偶数层入栈2,因为栈先进后出,所以偶数层的节点是倒叙的;偶数层先访问右子树再放问左子树,得到的顺序就又是正序了。
import java.util.*;
import java.util.ArrayList;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>>list=new ArrayList<>();
//双栈
Stack<TreeNode>stack1=new Stack<>();
Stack<TreeNode>stack2=new Stack<>();
if(pRoot==null){
return list;
}
stack1.push(pRoot);
while(!stack1.isEmpty()||!stack2.isEmpty()){
ArrayList<Integer>nodelist=new ArrayList<>();
//奇数层
while(!stack1.isEmpty()){
TreeNode node=stack1.pop();
nodelist.add(node.val);
if(node.left!=null){
stack2.push(node.left);
}
if(node.right!=null){
stack2.push(node.right);
}
}
if(nodelist.size()!=0){
list.add(new ArrayList<Integer>(nodelist));
}
nodelist.clear();
//偶数层
while(!stack2.isEmpty()){
TreeNode node=stack2.pop();
nodelist.add(node.val);
if(node.right!=null){
stack1.push(node.right);
}
if(node.left!=null){
stack1.push(node.left);
}
}
if(nodelist.size()!=0){
list.add(new ArrayList<Integer>(nodelist));
}
nodelist.clear();
}
return list;
}
}
在这里我犯过一个错误,list.add(new ArrayList<Integer>(nodelist));
这一步直接添加nodelist而不是新建一个对象,结果下一步调用clear()方法后,nodelist变空了。
判断是否是完全二叉树
该题说到底其实也是层序遍历,稍有变化的是在层序遍历的过程中如果遇到了一个空节点,层序遍历就已经结束。如果后续还出现了空节点,那么就不是完全二叉树。
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* public TreeNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return bool布尔型
*/
public boolean isCompleteTree (TreeNode root) {
// write code here
//空树肯定是完全二叉树
if(root==null){
return true;
}
Queue<TreeNode>queue=new LinkedList<>();
boolean isEmpty=false;
//整体上还是层序遍历
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(node==null){
isEmpty=true;
continue;
}
//continue表示退出本次while循环,准备开始下一次while循环
//如果是完全二叉树,出现空节点表示该树已遍历完,是不会再次进入while循环的
//如果再进入while循环,那么必会执行下面语句,返回false,也说明了该树不是一个完全二叉树。
if(isEmpty){
return false;
}
queue.offer(node.left);
queue.offer(node.right);
}
return true;
}
}
求二叉树的最大深度
二叉树的深度就两个,左子树的深度和右子树的深度。求出最大值就可以了。
递归过程:要求出树的左子树深度,只需要求出左子树的左子树的深度;要求出树的右子树深度,只需要求出右子树的右子树的深度就可以了,以此类推。
递归结束条件:遍历到叶子节点。
public int maxDepth (TreeNode root) {
// write code here
if(root==null){
return 0;
}
int depth1=maxDepth(root.left)+1;s
int depth2=maxDepth(root.right)+1;
return depth1>depth2?depth1:depth2;
}
求二叉树中和为某一个值得路径
一条路径指的就是从树的根节点到叶子节点,怎么从根节点遍历到叶子节点呢,说到底还是递归。
递归过程:如果存在这样一条路径,那么二叉树中某一条路径的和减去根节点的值也会等于这个值减去根节点的值。
递归结束条件:树已遍历完但是仍没有满足条件的值,返回false,或者满足条件找到的该值;
关键代码:
public boolean hasPath(TreeNode root,int sum){
if(root==null{
return false;
}
if(root.left==null&&root.right==null&&root=sum){
return true;
}
return hasPath(root.left,sum-root.val)||hasPath(root.right,sum-root.val);
}
判断是否是对称二叉树
对称二叉树的特点就是:根左右和根右左遍历得到的结果是一样的;
怎么遍历:还是递归
public boolean isSymmetrical(TreeNode root){
return Recursion(root,root);
}
public boolean Recursion(TreeNode root1,TreeNode root2){
if(root1==null&&root2==null){
return true;
}
if(root1==null||root2==null||root1.val!=root2.val){
return false;
}
return Recursion(root1.left,root2.right)&&Recursion(root1.right,root2.left);
}
求二叉树的镜像
和上题有相似之处。
求镜像就是把每个节点的左右子树进行交换。
自上向下考虑层序遍历,自底向上考虑递归。
如果该题自上向下,下面的子树并不好交换。
所以考虑递归:
特判:如果pRoot为空,返回空;
把pRoot的左子树放到Mirror中镜像一下;
把pRoot的右子树放到Mirror中镜像一下;
交换左右子树;
返回根节点root;
public TreeNode Mirror(TreeNode root){
if(root==null){
return null;
}
TreeNode left=Mirror(root.left);
TreeNode right=Mirror(root.right);
root.left=right;
root.right=left;
return root;
}