树
二叉树的下一个结点
题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
树的结点定义如下
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
分析

所有的结点根据其有无右子树,可以分为四种情况。之所以不考虑其左子树是因为是中序遍历,即使有左子树,左子树中的结点都在该节点之前遍历完了,不会对下一个结点产生影响。
四种情况分别为:
Case1: 给定的结点有右子树,右子树中存在结点有左孩子
下一个节点就是右子树中的最左结点。
示例: 结点 b
b有右孩子e,e有左孩子g,g为b的右子树中的最左结点,所以b的下一个结点为g。
Case2: 给定的结点有右子树,右子树中不存在结点有左孩子
下一个节点该结点的右孩子。
示例: 结点 c
c有右孩子f,f有右孩子i,所以b的右子树中不存在结点有左孩子,所以c的下一个结点是f。
Case3: 给定的结点无右子树,且该结点是其父结点的左孩子、
下一个结点就是其父结点。
示例: 结点 d
d没有右孩子,但是d是b的左孩子,所以d的下一个结点是b。
Case4: 给定的结点无右子树,且该结点是其父结点的右孩子,则向上追溯,直到其某个祖先结点是其父结点的左孩子,则下个结点是该祖先结点的父结点。若追溯不到满足条件的结点,则表示中序遍历结束。
示例1: 结点 h
h没有右子树,但h是e的右孩子,继续向上追溯,e是b的右孩子,直到追溯到b是a的左孩子,则h的下个结点是a;
示例2: 结点 i
i没有右子树,但是i是f的右孩子,继续向上追溯,f是c的右孩子,c是a的右孩子,而a是根节点,所以i后面没有节点了。

Java代码
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode == null)
return null;
TreeLinkNode p = pNode;
if(pNode.right != null){
pNode = pNode.right;
while(pNode.left==null && pNode.right!=null)
pNode = pNode.right;
// case 1
if(pNode.left != null)
p = pNode.left;
// case 2
else
p = p.right;
}
else if(pNode.next != null) {
if(pNode.next.left == pNode)
return pNode.next;
else{
while(pNode.next!=null && pNode.next.left!=pNode)
pNode = pNode.next;
// case 3
if(pNode.next != null)
p = pNode.next;
// case 4
else
p = null;
}
}
// 该结点为根节点,且没有右孩子
else
p = null;
return p;
}
}
对称的二叉树
题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
树的结点的定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
如下图所示

第一棵树是对称的,第二棵和第三棵不是对称的。
已知树的三种遍历方法:先序遍历、中序遍历、后序遍历都是先访问左子结点,再访问右子结点,以先序遍历为例,第一棵树的遍历结果为:8657675。
若定义一种新的遍历方式,按照“根-右-左”的顺序进行遍历,结果为:8657875,和先序遍历的结果是一样的。
注意:对于像第三棵树这样的不是完美二叉树,将空结点用“null”代替。
Java代码
public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot == null)
return true;
return symmetrical(pRoot, pRoot);
}
boolean symmetrical(TreeNode p1, TreeNode p2){
// 形状
// 1. p1和p2都为空
if(p1==null && p2==null)
return true;
// 2. p1或p2一个为空一个不为空;
if(p1==null || p2==null)
return false;
// 数值
if(p1.val != p2.val)
return false;
return symmetrical(p1.left, p2.right) && symmetrical(p1.right, p2.left);
}
}
把二叉树打印成多行
题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
树的结点的定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析

有两种方法,第一种方法是递归,第二种方法类似于层次遍历。
方法1:递归
递归的时候判断当前结点所在的深度,如果深度增加了,就将ArrayList进行扩容。
方法2:层次遍历
每遍历一个层次的结点的时候,就依次记录下其左右结点,并统计下一层的结点总数。
Java代码
方法1:递归
import java.util.ArrayList;
public class Solution {
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
visit(pRoot, 0, res);
return res;
}
public void visit(TreeNode root, int depth, ArrayList<ArrayList<Integer>> res){
if(root == null)
return;
if(depth >= res.size())
res.add(new ArrayList<Integer>());
res.get(depth).add(root.val);
visit(root.left, depth+1, res);
visit(root.right, depth+1, res);
}
}
方法2:层次遍历
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
if(pRoot == null)
return res;
// 队列用于层次遍历
Queue<TreeNode> queue = new LinkedList<TreeNode>();
// list用于记录当前行的结果
ArrayList<Integer> list = new ArrayList<Integer>();
queue.add(pRoot);
int cntOfCurLayer = 1;
int cntOfNextLayer = 0;
while(!queue.isEmpty()){
// 层次遍历
TreeNode temp = queue.remove();
if(temp.left != null){
queue.add(temp.left);
cntOfNextLayer++;
}
if(temp.right != null){
queue.add(temp.right);
cntOfNextLayer++;
}
list.add(temp.val);
cntOfCurLayer--;
if(cntOfCurLayer == 0){
res.add(list);
list = new ArrayList<Integer>();
cntOfCurLayer = cntOfNextLayer;
cntOfNextLayer = 0;
}
}
return res;
}
}
按之字形打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
树的结点的定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
使用两个辅助栈s1、s2和一个整型flag。
flag=0用来标志从左向右读,flag=1表示从右向左读。
初始化,将根节点入栈s1,flag=0;
flag=0时,从s1中依次pop出结点temp,再将temp的左右孩子入栈s2,由于下一轮是从右向左读,且stack是先进后出,所以先入栈左孩子,再入栈右孩子。
flag=1时,从s2中依次pop出结点temp,与上述相反,先入栈右孩子,再入栈左孩子。

输出结果为{{8}, {10, 6}(5,7,9,4)}
Java代码
public class Solution {
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
if(pRoot == null)
return res;
int flag = 0;
Stack<TreeNode> s1 = new Stack<TreeNode>();
Stack<TreeNode> s2 = new Stack<TreeNode>();
s1.push(pRoot);
ArrayList<Integer> list = new ArrayList<Integer>();
while(!s1.empty() || !s2.empty()){
if(flag == 0){
while(!s1.empty()){
TreeNode temp = s1.pop();
list.add(temp.val);
if(temp.left != null)
s2.push(temp.left);
if(temp.right != null)
s2.push(temp.right);
}
}
else{
while(!s2.empty()){
TreeNode temp = s2.pop();
list.add(temp.val);
if(temp.right != null)
s1.push(temp.right);
if(temp.left != null)
s1.push(temp.left);
}
}
res.add(list);
list = new ArrayList<Integer>();
flag = 1 - flag;
}
return res;
}
}
序列化二叉树
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树。
- 对于序列化:使用前序遍历,递归的将二叉树的值转化为字符,并且在每次二叉树的结点不为空时,在转化val所得的字符之后添加一个’ , '作为分割。对于空节点则以 ‘#’ 代替。
- 对于反序列化:按照前序顺序,递归的使用字符串中的字符创建一个二叉树。
树的结点的定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析

使用递归的思想。
- 序列化的时候,当一个结点不是空结点就将其添加到序列中,并递归处理其左孩子和右孩子。如果结点为空,则将"#"添加到序列中。
- 反序列化的时候,将字符串处理根据逗号split成String数组,然后进行递归处理。如果读取的字符串不为“#”,表示这个结点存在,然后递归处理其左孩子和右孩子。否则说明这是个空结点,返回null。
注意:在序列化的时候,使用StringBuilder而不是String来传递参数。可参考:String,StringBuffer与StringBuilder的区别??
Java代码
public class Solution {
String Serialize(TreeNode root) {
if(root == null)
return "";
StringBuilder str = new StringBuilder();
serializeCore(root, str);
return str.toString();
}
void serializeCore(TreeNode root, StringBuilder str){
if(root == null){
str.append("#,");
return;
}
str.append(root.val);
str.append(",");
serializeCore(root.left, str);
serializeCore(root.right, str);
}
TreeNode Deserialize(String str) {
if(str.length() == 0)
return null;
String[] strs = str.split(",");
return deserializeCore(strs);
}
int idx = -1;
TreeNode deserializeCore(String[] strs){
idx++;
if(!strs[idx].equals("#")){
TreeNode root = new TreeNode(0);
root.val = Integer.parseInt(strs[idx]);
root.left = deserializeCore(strs);
root.right = deserializeCore(strs);
return root;
}
return null;
}
}
二叉搜索树的第k个结点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
分析

二叉搜索树的中序遍历结果是从小到大排序的,所以二叉搜索树的第k小的结点就是其中序遍历的第k个元素。
Java代码
public class Solution {
int i = 0;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot != null){
TreeNode node = KthNode(pRoot.left, k);
if(node != null)
return node;
i++;
if(i == k)
return pRoot;
node = KthNode(pRoot.right, k);
if(node != null)
return node;
}
return null;
}
}
数据流中的中位数
题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
分析
参考:剑指Offer 【面试题63-数据流中的中位数】
使用最大堆和最小堆来过滤数据。
最大堆用来存放较小的数字,从大到小排列,则其根节点是较小数字中的最大值;
最小堆用来存放较大的数字,从小到大排列,则其根节点是较大数字中的最小值。
保证最小堆中的元素都大于最大堆的元素。
当读入一个新的数据前,
- 已经读取了偶数个数据,则将新数据放到最大堆中,然后再将最大堆中的最大值放到最小堆中,中位数是最小堆的根元素;
- 已经读取了奇数个数据,则将新数据放到最小堆中,然后再将最小堆中的最小值放到最大堆中,中位数是两个根元素的平均值;
示例:假设数据流为[5,2,3,4,1,6,7,0,8],

在Java中,使用PriorityQueue来表示最大堆和最小堆。
堆是树的应用,虽然没有直接用到树的结构,但是这个方法和使用平衡的二叉搜索树AVL的时间复杂度一样:
- 插入的时间复杂度为O(logn)
- 得到中位数的时间复杂度为O(1)。
Java代码
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
return y - x;
}
};
private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(comparator);
private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
int readyCnt = 0;
public void Insert(Integer num) {
if(readyCnt%2 == 0){
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
}
else{
minHeap.offer(num);
maxHeap.offer(minHeap.poll());
}
readyCnt++;
}
public Double GetMedian() {
if(readyCnt%2 == 0)
return new Double(maxHeap.peek() + minHeap.peek()) / 2;
else
return new Double(minHeap.peek());
}
}
本文深入探讨了二叉树的多种算法,包括查找中序遍历下一个结点、判断二叉树是否对称、多行打印、之字形打印、序列化与反序列化、查找第k小结点及数据流中位数等,提供了详细的分析和Java代码实现。
7545

被折叠的 条评论
为什么被折叠?



