二叉树的结构定义如下:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
序列化二叉树
实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 , 表示一个结点值的结束(value,)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
public class Solution {
StringBuilder sb = new StringBuilder();
//序列化,先序遍历
String Serialize(TreeNode root) {
if(root==null){
sb.append("#,");
return sb.toString();
}
sb.append(root.val+",");
Serialize(root.left);
Serialize(root.right);
return sb.toString();
}
//反序列化,先序遍历
int index = -1;
TreeNode Deserialize(String str) {
index++;
if(index >= str.length()){
return null;
}
String[] s = str.split(",");
TreeNode node = null;
if(!s[index].equals("#")){
node = new TreeNode(Integer.valueOf(s[index]));
node.left = Deserialize(str);
node.right = Deserialize(str);
}
return node;
}
}
二叉搜索树的第k个节点
给定一棵二叉搜索树,请找出其中的第k小(从小到大排列,第k个节点)的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
import java.util.Stack;
public class Solution {
int count = 0;
// 方法一、递归
// 当index == k时,return root返回的是当前节点,return node的作用是结束递归
TreeNode KthNode(TreeNode root, int k){
if(root != null){ //中序遍历寻找第k个
TreeNode node = KthNode(root.left,k);
if(node != null)
return node;
index ++;
if(index == k)
return root;
node = KthNode(root.right,k);
if(node != null)
return node;
}
return null;
}
// 方法二、非递归(借助栈)
TreeNode KthNode(TreeNode node, int k){
if(pRoot==null || k<=0){
return null;
}
Stack<TreeNode> s = new Stack<>();
s.push(node);
while(node.left!=null){
s.push(node.left);
node = node.left;
}
while(!s.isEmpty()){
TreeNode cur = s.pop();
count++;
if(count==k){
return cur;
}
if(cur.right!=null){
s.push(cur.right);
cur = cur.right;
while(cur.left!=null){
s.push(cur.left);
cur = cur.left;
}
}
}
return null;
}
}
把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
// 用队列存储每行的节点
// start标记正在处理的节点,end标记每层的节点数
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
if(pRoot==null){
return res;
}
int start = 0;
int end = 1;
Queue<TreeNode> que = new LinkedList<>();
que.add(pRoot);
while(!que.isEmpty()){
TreeNode node = que.poll();
list.add(node.val);
start++;
if(node.left!=null){
que.add(node.left);
}
if(node.right!=null){
que.add(node.right);
}
if(start==end){
res.add(list);
list = new ArrayList<>();
end = que.size();
start=0;
}
}
return res;
}
}
按之字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
// 用两个栈分别存储奇偶层的节点
// 当前栈为空时即为一层节点遍历结束,可加入res集合中(list集合不为空)
// 奇数层弹栈后向偶数层添加左右节点(先左后右)
// 偶数层弹栈后向奇数层添加左右节点(先右后左)
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> list;
if(pRoot==null){
return res;
}
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(pRoot);
while(!stack1.isEmpty() || !stack2.isEmpty()){
list = new ArrayList<>();
while(!stack1.isEmpty()){
TreeNode node = stack1.pop();
list.add(node.val);
if(node.left!=null){
stack2.push(node.left);
}
if(node.right!=null){
stack2.push(node.right);
}
}
if(!list.isEmpty()){
res.add(list);
}
list = new ArrayList<>();
while(!stack2.isEmpty()){
TreeNode node = stack2.pop();
list.add(node.val);
if(node.right!=null){
stack1.push(node.right);
}
if(node.left!=null){
stack1.push(node.left);
}
}
if(!list.isEmpty()){
res.add(list);
}
}
return res;
}
}
对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
public class Solution {
// 注意判断条件,仅当左子树和右子树同时为空(即遍历结束)时才返回true
// 当 left.val==right.val时,继续遍历其各自的左右子树是否对应相等
// 对应关系:左子树的左节点和右子树的右节点值相等,左子树的右节点和右子树的左节点相等
// return judge(left.left,right.right) && judge(left.right,right.left);
boolean isSymmetrical(TreeNode pRoot){
if(pRoot==null){
return true;
}
return judge(pRoot.left, pRoot.right);
}
boolean judge(TreeNode left, TreeNode right){
if(left==null){
if(right==null){
return true;
}
return false;
}
if(right==null){
return false;
}
if(left.val==right.val){
return judge(left.left,right.right) && judge(left.right,right.left);
}
return false;
}
}
二叉树的深度
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度(路径上的节点数)。
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
// 方法一:递归
public int TreeDepth(TreeNode root) {
if(root==null){
return 0;
}
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return Math.max(left,right)+1;
}
// 方法二:非递归
public int TreeDepth(TreeNode root){
if(root==null){
return 0;
}
Queue<TreeNode> que = new LinkedList<>();
que.add(root);
int depth = 0;
int start = 0;
int end = 1;
while(!que.isEmpty()){
TreeNode node = que.poll();
start++;
if(node.left!=null){
que.add(node.left);
}
if(node.right!=null){
que.add(node.right);
}
if(start==end){
depth++;
start = 0;
end = que.size();
}
}
return depth;
}
}
二叉树的节点数
输入一棵二叉树,求该二叉树的节点数。
public class Solution {
// 对比计算二叉树的深度
public int countNode(Node root){
if (root==null){
return 0;
}
int left = countNode(root.left);
int right = countNode(root.right);
return left + right + 1;
}
}
平衡二叉树
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
public class Solution {
// 方法一、自顶向下遍历
// 遍历每个结点,借助一个获取树深度的递归函数
// 根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断。
// 缺点:在判断上层结点的时候,会多次重复遍历下层结点
public boolean IsBalanced_Solution(TreeNode root) {
if(root==null){
return true;
}
return Math.abs(countDepth(root.left)-countDepth(root.right))<=1 &&
IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
public int countDepth(TreeNode node){
if(node==null){
return 0;
}
return Math.max(countDepth(node.left),countDepth(node.right))+1;
}
// 方法二、自底向上遍历,在获取树深度的同时,添加判断条件
// 如果子树是平衡二叉树,则返回子树的高度;
// 如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。
public boolean IsBalanced_Solution(TreeNode root){
return getDepth(root)!=-1;
}
public int getDepth(TreeNode node){
if(node==null){
return 0;
}
int left = getDepth(node.left);
if(left==-1){
return -1;
}
int right = getDepth(node.right);
if(right==-1){
return -1;
}
return Math.abs(left-right)<=1 ? Math.max(left,right)+1 : -1;
}
}
二叉树的两个节点之间最远的距离
问题:如果我们把二叉树看做图,父子节点之间的连线看成是双向的,我们姑且定义“距离”为两个节点之间边的个数。写一个程序求一棵二叉树中相距最远的两个节点之间的距离。
如下图所示,树中相距最远的两个节点为A,B,最大距离为6。
分析:计算一个二叉树的最大距离有两个情况:
- 情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点;
- 情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。
思路:递归求解每个节点的左、右子树的深度,不断更新全局变量maxLen,其值为Math.max(maxLen,(left+right));
public class Solution {
private int maxLen = 0;
public int findMaxLen(Node root){
if(root==null){
return 0;
}
int left = findMaxLen(root.left);
int right = findMaxLen(root.right);
int temp = left+right;
if(temp > maxLen){
maxLen = temp;
}
return Math.max(left,right)+1;
}
}
二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像,例如:
源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
public class Solution {
// 先序遍历二叉树的每个节点,如果当前节点存在子节点(左节点或者右节点),则交换两节点
// 递归交换所有非叶子节点的左右节点后,就得到了源二叉树的镜像二叉树
public void Mirror(TreeNode root) {
if(root==null){
return;
}
if(root.left==null && root.right==null){
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if(root.left!=null){
Mirror(root.left);
}
if(root.right!=null){
Mirror(root.right);
}
}
}
二叉树的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
public class Solution {
// 分两种情况:
// 1) 当前节点存在右子树,则右子树的最左子树即为中序遍历的下一个节点;
// 2) 当前节点不存在右子树,向上查找满足当前节点的父节点的左节点即为当前节点的那个节点,其父节点即为所求节点。
// while(pNode.next!=null && pNode.next.left!=pNode)
// 注意:根节点的next节点为null
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 && pNode.next.left!=pNode){
pNode = pNode.next;
}
return pNode.next;
}
}
二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:线索二叉树(在传统二叉树的基础上,利用空的指针域,当一个节点的左孩子为空时,让其指向其前驱节点;当节点的右孩子为空时,让其指向后继节点,前驱和后继节点,可以按照先序、中序或者后序的顺序来判定),就是把二叉树变成了一个双向链表 。
二叉搜索树按中序遍历构建线索二叉树得到的就是排序的双向链表
方法1、中序遍历二叉树,将其节点添加至链表中,遍历链表,将当前节点的右孩子指向后一个节点,后一个节点的左孩子指向当前节点;
方法 2、中序遍历(先右后左得到降序,若先左后右得到的是升序,pre指向最大节点),在递归遍历的过程中,pre作为前一个节点,pRootOfTree作为当前节点,当pre不为空时,pRootOfTree.right=pre; pre.left=pRootOfTree; pre移动赋值:pre = pRootOfTree,递归结束条件是pRootOfTree==null,返回值是pre(这时pre指向的的最小节点)
import java.util.ArrayList;
public class Solution {
// 方法一
ArrayList<TreeNode> list = new ArrayList<>();
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null){
return null;
}
inOrder(pRootOfTree);
for(int i=0; i<list.size()-1; i++){
list.get(i).right = list.get(i+1);
list.get(i+1).left = list.get(i);
}
return list.get(0);
}
public void inOrder(TreeNode root){
if(root==null){
return;
}
inOrder(root.left);
list.add(root);
inOrder(root.right);
}
/*
//方法二
TreeNode pre = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null){
return null;
}
Convert(pRootOfTree.right);
if(pre!=null){
pRootOfTree.right = pre;
pre.left = pRootOfTree;
}
pre = pRootOfTree;
Convert(pRootOfTree.left);
return pre;
}
*/
}
二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
public class Solution {
// 数组为空时返回
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null || sequence.length==0){
return false;
}
if(sequence.length==1){
return true;
}
return verifySquenceOfBST(sequence, 0, sequence.length-1);
}
// 后序遍历得到的序列最后一个值是当前子树的根节点
// 从根节点向前搜索,找到第一个小于根节点的值,其左(包括自身)为当前根节点的左子树,其右为右子树
// 更新区间的左右边界, 递归判断:左子树区间的所有结点值 < 根结点值 < 右子树区间所有结点值,这个条件是否满足
// 注意:判断成立的递归出口为start>=end(不是start==end)
public boolean verifySquenceOfBST(int[] sequence, int start, int end){
if(start>=end){
return true;
}
int base = sequence[end];
int i = end-1;
for(; i>=start; i--){
if(sequence[i]<base){
break;
}
}
for(int j=i-1; j>=start; j--){
if(sequence[j]>base){
return false;
}
}
return verifySquenceOfBST(sequence, i+1, end-1) && verifySquenceOfBST(sequence, start, i);
}
}
重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre==null || in == null || pre.length==0 || in.length==0){
return null;
}
TreeNode root = reConstructBinaryTree(pre, 0, pre.length-1, in, 0, in.length-1);
return root;
}
/* 先序遍历第一个位置肯定是根节点node,
中序遍历的根节点位置在中间p,在p左边的肯定是node的左子树的中序数组,p右边的肯定是node的右子树的中序数组
另一方面,先序遍历的第二个位置到p,也是node左子树的先序子数组,剩下p右边的就是node的右子树的先序子数组
把四个数组找出来,分左右递归调用即可
注意:
1)根节点左子树的pre数组的右边界为startPre+i-startIn(而不是startPre+i);
2)递归出口的判断条件为 startPre > endPre || startIn > endIn, 当相等时,仍需要建立左节点或者右节点。
*/
private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn){
if(startPre > endPre || startIn > endIn){
return null;
}
TreeNode root = new TreeNode(pre[startPre]);
int i=startIn;
for(; i<=endIn; i++){
if(in[i] == pre[startPre]){
break;
}
}
root.left = reConstructBinaryTree(pre, startPre+1, startPre+i-startIn, in, startIn, i-1);
root.right = reConstructBinaryTree(pre, i-startIn+startPre+1, endPre, in, i+1, endIn);
return root;
}
}
树的子结构
问题:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
- 子树:只要包含了一个结点,就得包含这个结点下的所有节点。
- 子结构:包含了一个结点,可以只取左子树或者右子树,或者都不取。
若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:
1)先序遍历树 A 中的每个节点node;
2)判断树 A中以node为根节点的子树是否包含树 B(当node2=null时,返回true;当node1=null || node.val != node2.val 时返回false,递归遍历左右子树
return recur(A.left, B.left) && recur(A.right, B.right); )。
若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
- 以 节点 A 为根节点的子树 包含树 B ,对应 recur(A, B);
- 树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
- 树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B);
// 判断一个树是否是另一个树的子结构
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1==null || root2==null){
return false;
}
return hasSubtree(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
public boolean hasSubtree(TreeNode root1, TreeNode root2){
if(root2==null){
return true;
}
if(root1==null || root1.val != root2.val){
return false;
}
return hasSubtree(root1.left,root2.left) && hasSubtree(root1.right,root2.right);
}
}
// 判断一个树是否是另一个树的子树
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1==null || root2==null){
return false;
}
return hasSubtree(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
public boolean hasSubtree(TreeNode root1, TreeNode root2){
if(root1==null && root2==null){
return true;
}
if(root1==null || root2==null || root1.val != root2.val){
return false;
}
return hasSubtree(root1.left,root2.left) && hasSubtree(root1.right,root2.right);
}
}