目录
面试题7:重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
分析:前序遍历的第一个结点为二叉树的根结点,然后在中序遍历中找出根结点的位置即可确定左、右子树节点的数量,递归构造即可。
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length != in.length|| pre.length == 0)
return null;
return constructCore(pre,0,pre.length-1,in,0,in.length-1);
}
private TreeNode constructCore(int[] pre, int ps, int pend, int[] in, int ins, int inend) {
if(ps > pend || ins > inend)
return null;
TreeNode root = new TreeNode(pre[ps]);
int size = 0;
for(int i = ins;i <= inend;i++){
if(in[i] == pre[ps])
break;
size++;
}
root.left = constructCore(pre,ps + 1,ps + size,in,ins,ins + size - 1);
root.right = constructCore(pre,ps + size + 1,pend,in,ins + size + 1,inend);
return root;
}
面试题8:二叉树的下一个节点
题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析:若该节点有右子树,那么下一个节点就是它的右子树的最左子节点;若没有右子树,再分两种情况。如果它是父节点的左子节点,那么下一个节点就是它的父节点;如果它是父节点的右子节点,则需一直向上遍历找到一个是它父节点的左子节点的结点,这个结点的父节点就是要找的下一个节点。
public TreeLinkNode GetNext(TreeLinkNode pNode){
if(pNode == null)
return null;
TreeLinkNode node = null;
if(pNode.right != null){ //有右子树
node = pNode.right;
while(node.left != null){
node = node.left;
}
}
else if(pNode.next != null){ //无右子树
if(pNode.next.left == pNode)//为父节点的左子树
node = pNode.next;
else{//为父节点的右子树
node = pNode;
while(node.next != null && node.next.left != node)
node = node.next;
node = node.next;
}
}
return node;
}
面试题26:树的子结构
题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
分析:分成两步,在树A中找到和树B的根结点的值一样的节点R,再判断树A中以R为根结点的子树是不是包含和树B一样的结构,递归的终止条件是到达了树A或树B的叶节点。注意:如果节点值的类型为double,在判断两个结点的值是否相等时不能用==来判断,只能通过它们的差的绝对值是否小于0.0000001来判断。
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean result = false;
if(root1 != null && root2 != null){
if(root1.val == root2.val)
result = doesTree1HavaTree2(root1,root2);
if(! result)
result = HasSubtree(root1.left,root2);
if(! result)
result = HasSubtree(root1.right,root2);
}
return result;
}
private boolean doesTree1HavaTree2(TreeNode root1, TreeNode root2) {
if(root2 == null)
return true;
if(root1 == null)
return false;
if(root1.val != root2.val)
return false;
return doesTree1HavaTree2(root1.left,root2.left)
&& doesTree1HavaTree2(root1.right,root2.right);
}
面试题27:二叉树的镜像
题目:操作给定的二叉树,将其变换为源二叉树的镜像。二叉树的镜像定义:
源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树:
8
/ \
10 6
/ \ / \
11 9 7 5
分析:先序遍历这棵树的每个节点,若有子节点,则交换它的两个子节点,当交换完所有非叶节点的左、右子节点之后,即可得到。
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);
}
面试题28:对称的二叉树
题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
分析:对称的二叉树前序遍历与对称前序遍历序列一致,所以可以通过两种遍历是否一致来判断。
boolean isSymmetrical(TreeNode pRoot) {
return isSymmetrical(pRoot,pRoot);
}
private boolean isSymmetrical(TreeNode left, TreeNode right) {
if(left == null && right == null)
return true;
if(left == null || right == null)
return false;
if(left.val != right.val)
return false;
return isSymmetrical(left.left,right.right)
&& isSymmetrical(left.right,right.left);
}
面试题32-1:从上到下打印二叉树
题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
分析:考查二叉树的层次遍历,借助队列即可。
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if(root == null)
return result;
Queue<TreeNode> list = new LinkedList<>();
list.add(root);
while(! list.isEmpty()){
TreeNode temp = list.poll();
if(temp.left != null)
list.add(temp.left);
if(temp.right != null)
list.add(temp.right);
result.add(temp.val);
}
return result;
}
面试题32-2:之字形打印二叉树
题目:给定一个二叉树,返回该二叉树的之字形层序遍历,(从左向右,下一层从右向左,一直这样交替)例如:给定的二叉树是{3,9,20,#,#,15,7}, 3↵ / ↵ 9 20↵ / ↵ 15 7该二叉树之字形层序遍历的结果是[↵ [3],↵ [20,9],↵ [15,7]↵]
分析:将二叉树分成奇数层和偶数层,奇数层先进先出,偶数层先进后出。
public ArrayList<ArrayList<Integer>> zigzagLevelOrder(TreeNode root) {
ArrayList<ArrayList<Integer>> result = new ArrayList();
if(root == null)
return result;
ArrayList<Integer> list = new ArrayList();
LinkedList<TreeNode> queue = new LinkedList();
int row = 1;
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
list.clear();
while(size > 0){
TreeNode node = queue.poll();
size--;
if(node.left != null)
queue.offer(node.left);
if(node.right != null)
queue.offer(node.right);
if(row % 2 == 1)
list.add(node.val);
else list.add(0,node.val);
}
result.add(new ArrayList(list));
row++;
}
return result;
}
面试题33:二叉搜索树的后序遍历序列
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
分析:二叉搜索树在后序遍历得到的序列中,最后一个是根结点。数组中前面的部分可以分为两个部分,第一部分是左子树节点的值,都比根结点小,第二部分是右子树节点的值,都比根结点大。
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence == null || sequence.length == 0)
return false;
return VerifySquenceOfBST(sequence,0,sequence.length-1);
}
private boolean VerifySquenceOfBST(int[] sequence,int start, int end) {
if(start >= end)
return true;
int i = start;
for(;i < end;i++){
if(sequence[i] > sequence[end])
break;
}
int j = i;
for(;j < end;j++){
if(sequence[j] < sequence[end])
return false;
}
return VerifySquenceOfBST(sequence,start,i-1)
&& VerifySquenceOfBST(sequence,i,end-1);
}
面试题34:二叉树中和为某一值的路径
题目:输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
分析:回溯法。在前序遍历访问二叉树的某一结点时,将该节点加入到路径上,并累加该节点的值。如果该节点为叶节点,且路径中节点的值刚好等于输入的整数,则符合当前路径要求。
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
if(root == null)
return result;
findPath(root,target,list,result);
return result;
}
private void findPath(TreeNode root, int target, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> result) {
if(root.val > target)
return;
list.add(root.val);
if(root.left == null && root.right == null && target == root.val)
result.add(new ArrayList<>(list));
if(root.left != null)
findPath(root.left,target - root.val,list,result);
if(root.right != null)
findPath(root.right,target - root.val,list,result);
list.remove(list.size() - 1);
}
面试题36:二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
分析:按照中序遍历的顺序,当我们遍历转换到根结点时,它的左子树已经转换成一个排序的链表了,并且处在链表的最后一个节点是当前值最大的节点。然后将根结点和链表连接起来,此时最后一个节点为当前链表,接着再遍历转换右子树即可。
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return pRootOfTree;
TreeNode lastNodeInList = null;//指向双链表的尾节点
lastNodeInList = Convert(pRootOfTree,lastNodeInList);
//找双链表的头结点
TreeNode head = lastNodeInList;
while(head != null && head.left != null)
head = head.left;
return head;
}
private TreeNode Convert(TreeNode pRootOfTree, TreeNode lastNodeInList) {
TreeNode cur = pRootOfTree;
if(cur.left != null)
lastNodeInList = Convert(cur.left,lastNodeInList);
cur.left = lastNodeInList;
if(lastNodeInList != null)
lastNodeInList.right = cur;
lastNodeInList = cur;
if(cur.right != null)
lastNodeInList = Convert(cur.right,lastNodeInList);
return lastNodeInList;
}
面试题37:序列化二叉树
题目:请实现两个函数,分别用来序列化和反序列化二叉树。二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
分析:二叉树的序列化是从根节点开始的,因此可以根据前序遍历的顺序来序列化二叉树。序列化和反序列化的过程都是将二叉树分为三部分,先序列化/反序列化根节点之后再分别处理它的左、右子树。
String Serialize(TreeNode root) {
if(root == null)
return "#!";
String s = root.val + "!";
s += Serialize(root.left);
s += Serialize(root.right);
return s;
}
TreeNode Deserialize(String str) {
if(str == null || str.length() == 0)
return null;
String[] string = str.split("!");
return Deserialize1(string);
}
int index = -1;
TreeNode Deserialize1(String[] string){
index++;
if(! string[index].equals("#")){
TreeNode root = new TreeNode(Integer.parseInt(string[index]));
root.left = Deserialize1(string);
root.right = Deserialize1 (string);
return root;
}
return null;
}
面试题41:数据流中的中位数
题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
分析:用Java自带的小顶堆PriorityQueue,改写Comparator的compare方法后可实现大顶堆。首先要保证数据平均分配到两个堆中,可以在数据的总数目是偶数时把新数据插入最小堆;然后要保证最大堆中的数字都大于最小堆中的数据。
使用不同数据结构的时间复杂度分析表:
数据结构 | 插入的时间复杂度 | 得到中位数的时间复杂度 |
没有排序的数组 | O(1) | O(n) |
排序的数组 | O(n) | O(1) |
排序的链表 | O(n) | O(1) |
二叉搜索树 | 平均O(logn),最差O(n) | 平均O(logn),最差O(n) |
AVL树 | O(logn) | O(1) |
最大堆和最小堆 | O(logn) | O(1) |
private int count = 0;
private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(15, (o1, o2) -> o2 - o1);
public void Insert(Integer num) {
if(count % 2 != 0){
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
}
else{
minHeap.offer(num);
maxHeap.offer(minHeap.poll());
}
count++;
}
public Double GetMedian() {
if(count % 2 == 0)
return (minHeap.peek() + maxHeap.peek()) / 2.0;
else
return maxHeap.peek() * 1.0;
}
面试题54:二叉搜索树的第k个结点
题目:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
分析:二叉搜索树的中序遍历是递增排序的,只需中序遍历一次即可。
TreeNode KthNode(TreeNode pRoot, int k) {
if(pRoot == null || k <= 0)
return null;
Stack<TreeNode> stack = new Stack();
TreeNode cur = pRoot;
int index = 0;
while(!stack.empty() || cur != null){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
index++;
if(index == k)
return cur;
cur = cur.right;
}
return null;
}
面试题55-1:二叉树的深度
题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
分析:递归即可。
public int TreeDepth(TreeNode root) {
if(root == null)
return 0;
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return (left > right ? left : right) + 1;
}
面试题55-2:平衡二叉树
题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。如果每个节点的左右子树的深度相差都不超过1,则是一棵平衡二叉树。
分析:采用后序遍历的方式。如果子树是平衡二叉树,则返回子树的高度;如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null)
return true;
return getDepth(root) != -1;
}
private int getDepth(TreeNode root) {
if(root == null)
return 0;
int left = getDepth(root.left);
if(left == -1)
return -1;
int right = getDepth(root.right);
if(right == -1)
return -1;
if(Math.abs(left - right) > 1)
return -1;
return Math.max(left,right) + 1;
}