文章目录
重构二叉树(中等难度)
题目描述: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路分析: 前序遍历:从根节点开始,先访问根节点,然后左子树,再右子树;中序遍历:先左子树,再根节点,最后右子树;
算法思想:
(1)pre数组中的第一个节点则一定是root,并且root一定再in的中间位置,故在in中找到root所在的位置i,则in[0,i)为root的左子树,左子树的节点树为i,in[i+1,in.length)为root的右子树,右子树的长度为
(2)pre中的左子树为:pre[1,i+1),长度为i;右子树的范围为[i+1,pre.length]
(3)in中的左子树为:in[0,i);右子树为:in[i+1,in.length]
(4)递归遍历,直到pre和in数组为空才结束。

代码实现
import java.util.Arrays;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
//特判,空树
if(pre.length==0 || in.length==0){
return null;
}
TreeNode root=new TreeNode(pre[0]);//创建一个根节点
//在in中找到根节点
for(int i=0;i<in.length;i++){
if(in[i]==pre[0]){
//左子树,注意Arrys.copyOfRange为左闭又开空间
root.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,1+i),Arrays.copyOfRange(in,0,i));
root.right=reConstructBinaryTree(Arrays.copyOfRange(pre,1+i,pre.length),Arrays.copyOfRange(in,i+1,in.length));
break;//下次递归进入for循环,因此找完左右子树后就要结束整个循环。
}
}
return root;
}
}
树的镜像(简单)
题目 操作给定的二叉树,将其变换为源二叉树的镜像。

思路分析:使用递归的思想,明确递归的结束条件,和调用自己的条件;
(1)当root为空时,直接结束当前函数
(2)当前root的左节点和右节点交换
(3)从左子树开始使用同样的逻辑开始递归;从右子树开始使用同样的逻辑递归.
public class Solution {
public void Mirror(TreeNode root) {
if(root==null){
return ;
}
//交换左、右子节点
TreeNode tmp=root.left;
root.left=root.right;
root.right=tmp;
//递归左子树
if(root.left!=null){
Mirror(root.left);
}
//递归右子树
if(root.right!=null){
Mirror(root.right);
}
}
}
求树的深度(简单)
原理 利用递归的方式,使用递归时,首先得出递归的条件,然后进入递归调用;
import java.lang.Math.*;
public class Solution {
public int TreeDepth(TreeNode root) {
if(root==null){
return 0;
}
//左子树和右子树以同样的逻辑判断,没向下走一层,结果应该加一,每一次递归,针对的树的根节点
return (Math.max(TreeDepth(root.left),TreeDepth(root.right))+1);
}
}
树的平衡性检测(简单)
题目描述 给定一颗树,判断其是否为平衡二叉树
思路分析 平衡二叉树的定义:左右子树的深度差不能超过1,当root为null时,该树必为平衡二叉树,我们需要做的是:检测该树的所有左右子树,判断其是否符合平衡二叉树的定义。
编程的关键
(1)getDepth(),给定根节点,求该节点的深度;
(2)判断一颗树,是否为符合平衡二叉树的定义;
(3)按照相同的判断逻辑,递归调用,判断这颗树的左子树、右子树是否均为平衡二叉树;
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
//特判
if(root==null){
return true;
}
//判断当前树是否为平衡二叉树
if(Math.abs(getDepth(root.left)-getDepth(root.right))>1){
return false;
}
//递归判断当前节点的左、右子树是否都是平衡树
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
//获取一颗树的深度
public int getDepth(TreeNode root){
if(root==null){
return 0;
}
return Math.max(getDepth(root.left),getDepth(root.right))+1;
}
}
树的层序遍历(中等)
题目描述 层序遍历,从上到下,从左到右遍历二叉树,每一层的节点存储在一个list中,最后将所有的list存储到结果集中。
思路分析 需要用到两个队列作为缓冲,第一个队列用来缓存当前层的节点,第二个队列用来缓存当前层的下一个节点;每次循环,应该保证队列1中的所有元素都入了队列2,然后依次取出队列二中的元素放入list中;
使用到的java库函数:
(1)队列,其实现的是java.util.Queue接口和java.util.List接口,所以应该导入这两个包,故:Queue q=new LinkedList();
(2)队列常见的方法:q.offer()入队,q.poll()出队,q.peek()查看队首元素,但是不出队列。
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
//用于存储最后的结果集合
ArrayList<ArrayList<Integer>> listOuter=new ArrayList<ArrayList<Integer>>();
//队列1缓存每一层的值
Queue<TreeNode> queue=new LinkedList<TreeNode>();
//队列2缓存当前层的下一层的值
Queue<TreeNode> queue2=new LinkedList<TreeNode>();
if(pRoot==null){
return listOuter;
}
//1.根节点先入队列1
queue.offer(pRoot);
while(!queue.isEmpty()){
//一次性将队列1中的所有根节点都取出并放进队列2中
while(!queue.isEmpty()){
TreeNode curNode=queue.poll();
queue2.offer(curNode);
}
ArrayList<Integer> listInner=new ArrayList<Integer>();
//当前层的左右节点即为下一层的根节点
//
while(!queue2.isEmpty()){
TreeNode curNode2=queue2.poll();
listInner.add(curNode2.val);//缓存当前层的值
if(curNode2.left!=null){
queue.offer(curNode2.left);//存储下一层的根节点
}
if(curNode2.right!=null){
queue.offer(curNode2.right);//存储下一层的根节点
}
}
listOuter.add(listInner);
}
return listOuter;
}
}
//使用一个队列即可
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res=new ArrayList<Integer>();
if(root==null){
return res;
}
Queue<TreeNode> queue=new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()){
int size=queue.size();
for(int i=0;i<size;i++){
TreeNode curNode=queue.poll();
if(curNode.left!=null){
queue.add(curNode.left);
}
if(curNode.right!=null){
queue.add(curNode.right);
}
res.add(curNode.val);
}
}
return res;
}
}
二叉树的下一个节点(中等)
题目描述 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析 根据输入节点是否含有右子树来判断;
(1)如果输入参数含有右子树,则:找到右子树的最左边的叶子节点
(2)如果输入参数没有右子树,则:找向上寻他的祖宗节点,直到满足如下条件:当前节点必须是祖宗的左子节点。
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode){
//特判
if(pNode==null){
return null;
}
//如果存在右子树,则找到右子树最左边的节点;
if(pNode.right!=null){
//找右子树最深的左节点
TreeLinkNode node=pNode.right;
while(node.left!=null){
node=node.left;
}
return node;
}
//如果右子树子树为空,则一直循环向上找其祖宗节点,
//直到找到满足如下条件的祖宗节点为止:当前节点是该祖宗节点的左子节点
while(pNode.next!=null){
if(pNode.next.left==pNode){
return pNode.next;
}
pNode=pNode.next;
}
return null;
}
}
二叉排序树转换成双端链表(中等)
题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
例如二叉树为:


可以转化为:
思路分析:
先看下图,再解释:

关于二叉树的问题,基本上可以归纳为递归调用的问题,而递归调用的关键在于:让函数处理好当前节点 ,其他的就交给递归做就好了。这个问题可以这样分解:排序二叉树看成三个组成部分:根节点4,以2为根节点的左子树和以6为根节点的右子树。按照递归的思想,我们先处理好根节点,而至于另外两个子树则以相同的处理逻辑递归调用即可。同时,排序二叉树一定和中序遍历有关系,因此我们应该在中序遍历的过程中完成二叉树转化的过程,转换过程的另一个关键点在于:让左子树的最后一个节点lastNode的右指针指向当前节点,当前节点的左指针指向lastNode,当根节点4完成转化,后更新LastNode,即LastNode=Node;,由于这个lastNode依赖于左子树,所以应该先递归完成左子树,,此时再递归完成右子树(整体的框架为:中序遍历)
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
TreeNode lastNode=null;
public TreeNode Convert(TreeNode pRootOfTree){
if(pRootOfTree==null){
return null;
}
inorder(pRootOfTree);
//获取双端链表的头节点
while(lastNode.left!=null){
lastNode=lastNode.left;
}
return lastNode;
}
private void inorder(TreeNode node){
if(node==null){
return;
}
inorder(node.left);
//初始化,lastNode为当前节点,要不然会报空指针异常
if(lastNode==null){
lastNode=node;
}else{
node.left=lastNode;
lastNode.right=node;
lastNode=node;
}
inorder(node.right);
}
}
子树问题(简单)
问题描述: 输入两棵二叉树A,B,判断B是不是A的子结构。
树问题的处理方式: 归根结底,大部分二叉树的问题都可以使用递归的方式求解,求解过程可见树分成以下三个部分:根节点形成的当前树、根节点的左子树,根节点的右子树。我们递归的核心逻辑是:只处理当前的节点!只处理当前节点!只处理当前节点,由于其他节点的逻辑均一样,所以可以使用递归调用,分别传入root.left和root.right;
问题分析: 按照上述的思维方式,判断一树B是否为树A的子结构,需要解决两个大问题:
(1)如何判断两个树完全相同
- 特判:Bnull,则为true;B!=null && Anull 必为false;
- 使用递归的方式判断,两树的所有节点均相等(先当前节点,再递归左节点最后递归右节点)
(2)将树A分为三个部分:树A、左子树和右子树;接下来只要满足三个条件之一就能断定树B是树A的子树 - 树B和树A相同
- 树B和树A的左子树相同(递归)
- 树B和树A的右子树相同(递归)
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//判断root2是否为root1的子树
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//特判
if(root1==null || root2==null){
return false;
}
return isSame(root1,root2) || HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}
//判断两棵树是否相同
private boolean isSame(TreeNode root1,TreeNode root2){
//特判,如果B树为空,两者返回true
if(root2==null){
return true;
}
//B!==null && A==null,则B比不可能是A的子树,返回false
if(root1==null){
return false;
}
boolean res=true;
res=res && (root1.val==root2.val) && isSame(root1.left,root2.left) && isSame(root1.right,root2.right);
return res;
}
}
二叉搜索树的第k个节点
题目描述 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
思路分析: 单凡是BST问题,必然和中序遍历有着极大的联系,因为BST的中序遍历刚好就是各个节点从小到大的序列,这道的思路很简单,将这课树的中序遍历序列加入队列中(该过程在中序遍历的过程中完成),然后,读取到第K个节点即可;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
TreeNode KthNode(TreeNode pRoot, int k){
if(k<1){
return null;
}
Queue<TreeNode> queue=new LinkedList<TreeNode>();
inorder(pRoot,queue);
if(queue.size()<k){
return null;
}
while(k>1){
queue.poll();
k--;
}
return queue.poll();
}
//中序遍历root树,并依次入队列
private void inorder(TreeNode root,Queue queue){
if(root==null){
return;
}
inorder(root.left,queue);
//将当前节点加入队列
queue.offer(root);
inorder(root.right,queue);
}
}
二叉树的序列化与反序列化
题目描述: 请实现两个函数,分别用来序列化和反序列化二叉树
[注] 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
题目分析: 可以使用前、中、后、层序等4中遍历方式来实现,注意在序列化和反序列化的过程中他们所使用的遍历方式保持一致即可,同时要遵循相同的约定
用到的java工具包: StringBuilder、字符串中的常用方法;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private int index=-1;
String Serialize(TreeNode root) {
StringBuilder sb=new StringBuilder();
serializeHelper(root,sb);
return sb.toString();
}
//使用前序遍历进行序列化
private void serializeHelper(TreeNode root,StringBuilder sb){
//特判
if(root==null){
sb.append("#!");
return;
}
sb.append(root.val).append("!");
serializeHelper(root.left,sb);
serializeHelper(root.right,sb);
}
TreeNode Deserialize(String str) {
String[] strs=str.split("!");
return deserializeHelper(strs);
}
private TreeNode deserializeHelper(String[] strs){
index++;
//第一个节点不能是空节点
if(!strs[index].equals("#")){
TreeNode curNode=new TreeNode(Integer.parseInt(strs[index]));
curNode.left=deserializeHelper(strs);
curNode.right=deserializeHelper(strs);
return curNode;
}
return null;
}
}
Z字形打印树
题意描述: 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路分析: 该题的思路就是层序遍历,通过观察发现:当偶数层时,顺序加入list,当奇数层时,逆序加入list中,因此可以引入层数变量level 来记录当前是第几层。关于层序遍历的思路很简单:用一个队列缓冲,初始化时,将根节点入队列,计算当前队列中的size,每次从队列中取出size个的节点,这些节点则为当前层的节点,如果需要逆序打印,则有两种方式:
- list.add(0,node);每次从0索引开始加;
- 利用栈的特性,先入栈,然后再出栈
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> results=new ArrayList<ArrayList<Integer>>();
//特判
if(pRoot==null){
return results;
}
Queue<TreeNode> queue=new LinkedList<TreeNode>();
int level=0;
queue.offer(pRoot);
while(!queue.isEmpty()){
int size=queue.size();
ArrayList<Integer> list=new ArrayList<Integer>();
Stack<TreeNode> stack=new Stack<TreeNode>();
for(int i=0;i<size;i++){
TreeNode curNode=queue.poll();
if(level%2==0){
//偶数层,则顺序添加
list.add(curNode.val);
}else{//逆序放入list中
list.add(0,curNode.val);
//stack.add(curNode);
}
if(curNode.left!=null){
queue.offer(curNode.left);
}
if(curNode.right!=null){
queue.offer(curNode.right);
}
}
//while(!stack.isEmpty()){
//list.add(stack.pop().val);
//}
level++;
results.add(list);
}
return results;
}
}
对称二叉树
题目描述: 判断一棵树是否为对称二叉树,左右两节点围绕中轴线对称
解题思路: 先实现这么一个函数:如何判断两颗树是否对称,用递归的方式就是:如果两个根节点都为空,则必为对称,如果只有一个为空,则必然不对称,如果两个都不为空,需要判断两个根节点是否相等,然后使用相同的逻辑框架去递归判断左右子树,即:左子树的右节点要等于右子树的左节点,同时,右子树的左节点要等与左子树的右节点;,递归问题都是负责当前节点的问题;如何判断两颗树是否对称知道了,那么一颗树的对称不就是这棵树的左子树和右子树要对称吗?当然一定要有个特判:空树必然对称,只有一个子树的树必然不对称,其他情况再用上述函数进行判断。
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
boolean isSymmetrical(TreeNode pRoot){
if(pRoot==null){
return true;
}
if(pRoot.left==null && pRoot.right==null){
return true;
}
if(pRoot.left==null || pRoot.right==null){
return false;
}
return sysmmetrical(pRoot.left,pRoot.right);
}
//判断两颗树是否为对称的
private boolean sysmmetrical(TreeNode root1,TreeNode root2){
//两颗空树比对称
if(root1==null && root2==null){
return true;
}
//只有一个为空必然不对称
if(root1==null || root2==null){
return false;
}
//两个根节点不为空,则要求两个节点的值要相等;
return root1.val==root2.val && sysmmetrical(root1.left,root2.right) && sysmmetrical(root1.right,root2.left);
}
}
根节点到叶子节点和为sum的问题总结
是否存在路径
题目描述: 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
思路分析: 使用递归的方式来求解,当前节点的值如果是叶子节点并且其值为目标值,结束递归,找到;否则的话,在判断左子树到叶子节点的值是否为sum-target,同理,右子树也一样,两者是 || 的关系
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null)
return false;
int val = root.val;
if (root.left == null && root.right == null && val == sum)
return true;
return hasPathSum(root.left, sum - val) || hasPathSum(root.right, sum - val);
}
找到所有的路径
题目描述: 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
题目分析: 这道题和上一道题,核心思想是一样的,只是需要用一个list来存储当前找到的路径,由于java中的参数是值传递,所以每次进入递归调用的时候需要想复制上一段的list。
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
//记录总的路径
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
//记录每一条路径
ArrayList<Integer> list=new ArrayList<Integer>();
findPath(root,target,list,res);
return res;
}
private void findPath(TreeNode root,int target,ArrayList<Integer> list, ArrayList<ArrayList<Integer>> res){
if(root==null){
return;
}
if(root.left==null && root.right==null && root.val==target){
list.add(root.val);
res.add(list);
return;
}
//想加入根节点的值,继续向下递归
list.add(root.val);
if(root.left!=null){
//这是上一list的基础上进行,所以复制
ArrayList<Integer> leftList=new ArrayList<Integer>(list);
findPath(root.left,target-root.val,leftList,res);
}
if(root.right!=null){
//复制
ArrayList<Integer> rightList=new ArrayList<Integer>(list);
findPath(root.right,target-root.val,rightList,res);
}
}
}
使用到的一个list复制方法: new ArrayList(list);复制list;
计算路径的总条数
题目描述: 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径的总数目。
思路分析: 和上题一样,直接看代码
class Solution {
public int pathSum(TreeNode root, int sum) {
//特判
if(root==null){
return 0;
}
//从根路径开始找,利用递归的方式遍历每一个节点
int rootSum=findPathFromNode(root,sum);
int leftSum=pathSum(root.left,sum);
int rightSum=pathSum(root.right,sum);
return rootSum+leftSum+rightSum;
}
//从指点节点Node中向下寻找路径,满足路径和=sum
private int findPathFromNode(TreeNode node,int sum){
if(node==null){
return 0;
}
//当前节点的值==sum,则路径为1
int pathSum=node.val==sum?1:0;
//向左递归查找左子树是否能找到路径
int leftSum=findPathFromNode(node.left,sum-node.val);
//向右递归查找是否能找到路径
int rightSum=findPathFromNode(node.right,sum-node.val);
return leftSum+rightSum+pathSum;
}
}
BST后序遍历序列
题目描述: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路分析: BST的后序序列有如下特点:最后一个元素为根节点,剩余元素中比根节点的小的在左半部分(左子树),比根节点大的在右半部分(右子树),关键点在于找到左右子树的分界点;两个fo循环就能解决;对于左右子树,同样用递归去求解,这里面要注意递归的结束条件:start>=root,为什么呢?当start==root时,只有根节点,必为true,当start>root时,只有左子树;
public class Solution {
int index=0;
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null || sequence.length==0){
return false;
}
return verifyHelper(sequence,0,sequence.length-1);
}
private boolean verifyHelper(int[] sequence,int start,int root){
//只有根节点
if(start>=root){
return true;
}
int key=sequence[root];//数组最后一个值一定为根节点的值
//找到左、右子树的分界点
//i的左边全小于key,i的右边全大于key
int i=0;
for(i=start;i<root;i++){//[0,root)
if(sequence[i]>key){
break;
}
}
for(int j=i;j<root;j++){
if(sequence[j]<key){
return false;
}
}
//找到了i;左子树为[0,i-1],右子树为[i,root-1]
//使用递归的方式
return verifyHelper(sequence,start,i-1) && verifyHelper(sequence,i,root-1);
}
}
总结
所有的二叉树问题几乎都可以归咎于递归,递归的思想就是,当前函数只关注本节点的处理逻辑,而对左子树和右子树的处理逻辑均调用自己,直到递归的结束条件为止,因此,递归必然要先考虑结束条件,否则为死归!!!!!

715





