1.数据结构
1.1 TreeSet、TreeMap
1.2 HashMap、ConcurrentHashMap
2.编程题
Q1.实现一个函数,检查二叉树是否平衡。平衡树的定义:任意一个结点,其两棵子树的高度差不超过1。
Ans.思路:
- 可以直接递归处理。效率不高,getHeight会重复计算同一结点高度。算法复杂度为O(nlgn)。 T(n) = 2T(n/2) + kn。
- 可以在递归向上判断时,直接从底向上仅判断。省去重复判断。
不推荐的做法:
public static int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
}
public static boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
int heightDiff = getHeight(root.left) - getHeight(root.right);
if (Math.abs(heightDiff) > 1) {
return false;
}
else {
return isBalanced(root.left) && isBalanced(root.right);
}
}
优化的做法,递归方式的自底向上处理:
public static int checkHeight(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = checkHeight(root.left);
if (leftHeight == -1) {
return -1;
}
int rightHeight = checkHeight(root.right);
if (rightHeight == -1) {
return -1;
}
int heightDiff = leftHeight - rightHeight;
if (Math.abs(heightDiff) > 1) {
return -1;
}
else {
return Math.max(leftHeight, rightHeight) + 1;
}
}
public static boolean isBalanced(TreeNode root) {
if (checkHeight(root) == -1) {
return false;
} else {
return true;
}
}
Q2.给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉树。
Ans.思路
- 递归地让中间结点成为根节点。
public class TreeNode {
public int data;
public TreeNode left;
public TreeNode right;
public TreeNode parent;
private int size = 0;
public TreeNode(int d) {
data = d;
size = 1;
}
public void setLeftChild(TreeNode left) {
this.left = left;
if (left != null) {
left.parent = this;
}
}
public void setRightChild(TreeNode right) {
this.right = right;
if (right != null) {
right.parent = this;
}
}
private static TreeNode createMinimalBST(int arr[], int start, int end){
if (end < start) {
return null;
}
int mid = (start + end) / 2;
TreeNode n = new TreeNode(arr[mid]);
n.setLeftChild(createMinimalBST(arr, start, mid - 1));
n.setRightChild(createMinimalBST(arr, mid + 1, end));
return n;
}
public static TreeNode createMinimalBST(int array[]) {
return createMinimalBST(array, 0, array.length - 1);
}
Q3.给定一棵二叉树,设计一个算法,创建含有某一深度上所有结点的链表(比如,若一棵树的深度为D,则会创建D个链表,每一层都组成一个链表)
Ans.思路
- 使用DFS,记住深度即可,对于该深度的结点加入该深度的链表即可
- 使用BFS,记住上一层有哪一些,据此生成下一层。这里是直接将上一层赋值给parents。然后根据parents生成下一层。
使用DFS:
public class QuestionDFS {
public static void createLevelLinkedList(TreeNode root, ArrayList> lists, int level) {
if (root == null) return;
LinkedList list = null;
if (lists.size() == level) { // Level not contained in list
list = new LinkedList();
/* Levels are always traversed in order. So, if this is the first time we've visited level i,
* we must have seen levels 0 through i - 1. We can therefore safely add the level at the end. */
lists.add(list);
} else {
list = lists.get(level);
}
list.add(root);
createLevelLinkedList(root.left, lists, level + 1);
createLevelLinkedList(root.right, lists, level + 1);
}
public static ArrayList> createLevelLinkedList(TreeNode root) {
ArrayList> lists = new ArrayList>();
createLevelLinkedList(root, lists, 0);
return lists;
}
public static void printResult(ArrayList> result){
int depth = 0;
for(LinkedList entry : result) {
Iterator i = entry.listIterator();
System.out.print("Link list at depth " + depth + ":");
while(i.hasNext()){
System.out.print(" " + ((TreeNode)i.next()).data);
}
System.out.println();
depth++;
}
}
public static void main(String[] args) {
int[] nodes_flattened = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TreeNode root = AssortedMethods.createTreeFromArray(nodes_flattened);
ArrayList> list = createLevelLinkedList(root);
printResult(list);
}
}
使用BFS:
public class QuestionBFS {
public static ArrayList> createLevelLinkedList(TreeNode root) {
ArrayList> result = new ArrayList>();
/* "Visit" the root */
LinkedList current = new LinkedList();
if (root != null) {
current.add(root);
}
while (current.size() > 0) {
result.add(current); // Add previous level
LinkedList parents = current; // Go to next level
current = new LinkedList();
for (TreeNode parent : parents) {
/* Visit the children */
if (parent.left != null) {
current.add(parent.left);
}
if (parent.right != null) {
current.add(parent.right);
}
}
}
return result;
}
public static void printResult(ArrayList> result){
int depth = 0;
for(LinkedList entry : result) {
Iterator i = entry.listIterator();
System.out.print("Link list at depth " + depth + ":");
while(i.hasNext()){
System.out.print(" " + ((TreeNode)i.next()).data);
}
System.out.println();
depth++;
}
}
public static void main(String[] args) {
int[] nodes_flattened = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TreeNode root = AssortedMethods.createTreeFromArray(nodes_flattened);
ArrayList> list = createLevelLinkedList(root);
printResult(list);
}
}
Q4.实现一个函数,检查一棵二叉树是否为二叉查找树。
Ans.思路:
- 本质上是检查二叉树是否有序。因此直观的做法是检查中序遍历结果是否有序
实现上直观上可以使用数组;
优化做法是记住检查过的最右边的数,即当前最大数; - 可以从上到下,检查结点左子树和右子树是否在指定的范围内(min, max),计入左子树,更新max;进入右子树,更新min。
利用中序遍历:
public static Integer last_printed = null;
public static boolean checkBST(TreeNode n) {
if (n == null) {
return true;
}
// Check / recurse left
if (!checkBST(n.left)) {
return false;
}
// Check current
if (last_printed != null && n.data < last_printed) {
return false;
}
last_printed = n.data;
// Check / recurse right
if (!checkBST(n.right)) {
return false;
}
return true;
}
利用范围检查:
public static boolean checkBST(TreeNode n, Integer min, Integer max) {
if (n == null) {
return true;
}
if ((min != null && n.data < min) || (max != null && n.data > max)) {
return false;
}
if (!checkBST(n.left, min, n.data) ||
!checkBST(n.right, n.data, max)) {
return false;
}
return true;
}
public static boolean checkBST(TreeNode n) {
return checkBST(n, null, null);
}
Q5.设计一个算法,找出二叉查找树中指定结点的“下一个”结点(也即中序后继)。可以假定每个结点含有指向父节点的链接。
Ans.思路:
两种情况:
- 1)右子树的最小
- 2)没有右子树的时候,向上找到父节点,一直到结点是其父节点的左孩子
public static TreeNode inorderSucc(TreeNode n) {
if (n == null) return null;
// Found right children -> return left most node of right subtree
if (n.parent == null || n.right != null) {
return leftMostChild(n.right);
} else {
TreeNode q = n;
TreeNode x = q.parent;
// Go up until we�re on left instead of right
while (x != null && x.left != q) {
q = x;
x = x.parent;
}
return x;
}
}
public static TreeNode leftMostChild(TreeNode n) {
if (n == null) {
return null;
}
while (n.left != null) {
n = n.left;
}
return n;
}
Q6.设计并实现一个算法,找出二叉树中某两个结点的第一个共同祖先。不得将额外的结点存储在另外的数据结构中。注意:这不一定是二叉查找树。
Ans.思路:
- 1)对于有parent指针,转换成了两个链表是有交点的问题
- 2)对于没有parent
2-1)使用递归算法,从root开始向下。三种情况:A.若有结点不在树中,直接返回;B.若在同一个子树中,继续向下递归;C.若在不同子树,则当前root是第一个祖先。 复杂度:平衡树时 n + n/2 + n/4 ... = O(n)。
2-2)优化。A.若有结点不在树中,直接返回;B.优化关键:省略掉递归中对子树的反复搜索,将自顶向下的递归改为自底向上的递归。若自底向上找到了共同的父亲,则直接返回。
对于这种类型的递归优化,方式都是一样,就是将自顶向下的递归改为自底向上的递归。
自顶向下的递归:
public static boolean covers(TreeNode root, TreeNode p) {
if (root == null) return false;
if (root == p) return true;
return covers(root.left, p) || covers(root.right, p);
}
public static TreeNode commonAncestorHelper(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
boolean is_p_on_left = covers(root.left, p);
boolean is_q_on_left = covers(root.left, q);
if (is_p_on_left != is_q_on_left) { // Nodes are on different side
return root;
}
TreeNode child_side = is_p_on_left ? root.left : root.right;
return commonAncestorHelper(child_side, p, q);
}
public static TreeNode commonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (!covers(root, p) || !covers(root, q)) { // Error check - one node is not in tree
return null;
}
return commonAncestorHelper(root, p, q);
}
自底向上递归:
private static boolean covers(TreeNode root, TreeNode p) {
if (root == null) return false;
if (root == p) return true;
return covers(root.left, p) || covers(root.right, p);
}
private static TreeNode commonAncestorHelper(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root == p && root == q) {
return root;
}
TreeNode x = commonAncestorHelper(root.left, p, q);
if (x != null && x != p && x != q) { // Found common ancestor
return x;
}
TreeNode y = commonAncestorHelper(root.right, p, q);
if (y != null && y != p && y != q) {
return y;
}
if (x != null && y != null) {
return root; // This is the common ancestor
} else if (root == p || root == q) {
return root;
} else {
return x == null ? y : x;
}
}
public static TreeNode commonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (!covers(root, p) || !covers(root, q)) { // Error check - one node is not in tree
return null;
}
return commonAncestorHelper(root, p, q);
}
Q7.你有两棵非常大的二叉树:T1,有几百万个结点;T2,有几百个结点。设计一个算法,判断T2是否为T1的子树。
如果T1有这么一个结点n,其子树与T2一模一样,则T2为T1的子树。也就是说,从结点n处把树砍断,得到的树与T2完全相同。
Ans.思路:
- 1)一种直观的算法,直接递归在T1中查找T2。占用O(lgn + lgm)内存,时间O(n + km)
- 2)利用前序和中序生成的字符串来比较,并且要加入特殊字符表示NULL,否则无法区别存在相同值的情况。占用O(n + m)内存,时间O(n + m)
直观算法:
public static boolean containsTree(TreeNode t1, TreeNode t2) {
if (t2 == null)
return true; // The empty tree is a subtree of every tree.
else
return subTree(t1, t2);
}
/* Checks if the binary tree rooted at r1 contains the binary tree
* rooted at r2 as a subtree somewhere within it.
*/
public static boolean subTree(TreeNode r1, TreeNode r2) {
if (r1 == null)
return false; // big tree empty & subtree still not found.
if (r1.data == r2.data) {
if (matchTree(r1,r2)) return true;
}
return (subTree(r1.left, r2) || subTree(r1.right, r2));
}
/* Checks if the binary tree rooted at r1 contains the
* binary tree rooted at r2 as a subtree starting at r1.
*/
public static boolean matchTree(TreeNode r1, TreeNode r2) {
if (r2 == null && r1 == null)
return true; // nothing left in the subtree
if (r1 == null || r2 == null)
return false; // big tree empty & subtree still not found
if (r1.data != r2.data)
return false; // data doesn�t match
return (matchTree(r1.left, r2.left) &&
matchTree(r1.right, r2.right));
}
Q8.给定一棵二叉树,其中每个结点都含有一个数值。设计一个算法,打印结点数值总和等于某个给定值的所有路径。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束。
Ans.思路:
- 这题的直观思路是:从开始结点出发,然后向下遍历任意路径,相当于以自己为根的子树,并且必须检查到叶子。由于从起始点开始到叶子节点的检查有个特性:会不断有分支。编程不太方便。
- 逆向思维:对于每个结点,向上检查
- 这题是典型的暴力解法,穷举所有可能性,深度遍历解空间。
- 算法会递归二叉树的深度,假设是平衡树,则是O(lgn)的空间复杂度,时间复杂度对于每个结点来说耗费O(lgn),总共有n个结点,所以是O(nlgn)
public static void findSum(TreeNode node, int sum, int[] path, int level) {
if (node == null) {
return;
}
/* Insert current node into path */
path[level] = node.data;
int t = 0;
for (int i = level; i >= 0; i--){
t += path[i];
if (t == sum) {
print(path, i, level);
}
}
findSum(node.left, sum, path, level + 1);
findSum(node.right, sum, path, level + 1);
/* Remove current node from path. Not strictly necessary, since we would
* ignore this value, but it's good practice.
*/
path[level] = Integer.MIN_VALUE;
}
public static int depth(TreeNode node) {
if (node == null) {
return 0;
} else {
return 1 + Math.max(depth(node.left), depth(node.right));
}
}
public static void findSum(TreeNode node, int sum) {
int depth = depth(node);
int[] path = new int[depth];
findSum(node, sum, path, 0);
}
private static void print(int[] path, int start, int end) {
for (int i = start; i <= end; i++) {
System.out.print(path[i] + " ");
}
System.out.println();
}
Q9.有个简单的类似结点的数据结构BiNode,包含两个指向其他结点的指针。数据结构BiNode可以用来表示二叉树或双向链表。编写一个方法,将二叉查找树转换为双向链表。要求所有数值的排序不变,转换操作不得引入其他数据结构。
public class BiNode {
public BiNode node1;
public BiNode node2;
public int data;
public BiNode(int d) {
data = d;
}
}
Ans.思路:
- 使用一个包裹数据结构包含链表的头和尾,然后递归地进行链接即可
- 可以直接使用链表头也行,找链表尾需要遍历花点时间
- 将链表构造成环形链表,这样head.node1就能取到表尾
使用一个包裹数据结构包含链表的头和尾:
public class QuestionA {
private static class NodePair {
BiNode head;
BiNode tail;
public NodePair(BiNode head, BiNode tail) {
this.head = head;
this.tail = tail;
}
}
public static NodePair convert(BiNode root) {
if (root == null) {
return null;
}
NodePair part1 = convert(root.node1);
NodePair part2 = convert(root.node2);
if (part1 != null) {
concat(part1.tail, root);
}
if (part2 != null) {
concat(root, part2.head);
}
return new NodePair(part1 == null ? root : part1.head, part2 == null ? root : part2.tail);
}
public static void concat(BiNode x, BiNode y) {
x.node2 = y;
y.node1 = x;
}
只需要链表头:
public class QuestionB {
static int count = 0;
public static BiNode convert(BiNode root) {
if (root == null) {
return null;
}
BiNode part1 = convert(root.node1);
BiNode part2 = convert(root.node2);
if (part1 != null) {
concat(getTail(part1), root);
}
if (part2 != null) {
concat(root, part2);
}
return part1 == null ? root : part1;
}
public static BiNode getTail(BiNode node) {
if (node == null) {
return null;
}
while (node.node2 != null) {
count++;
node = node.node2;
}
return node;
}
public static void concat(BiNode x, BiNode y) {
x.node2 = y;
y.node1 = x;
}
使用环形链表处理,分清楚头和尾即可,头为part1 part3,尾为part1.node1和part3.node1
public static BiNode convertToCircular(BiNode root) {
if (root == null) {
return null;
}
BiNode part1 = convertToCircular(root.node1);
BiNode part3 = convertToCircular(root.node2);
if (part1 == null && part3 == null) {
root.node1 = root;
root.node2 = root;
return root;
}
BiNode tail3 = part3 == null ? null : part3.node1;
/* join left to root */
if (part1 == null) {
concat(part3.node1, root);
} else {
concat(part1.node1, root);
}
/* join right to root */
if (part3 == null) {
concat(root, part1);
} else {
concat(root, part3);
}
/* join right to left */
if (part1 != null && part3 != null) {
concat(tail3, part1);
}
return part1 == null ? root : part1;
}
public static BiNode convert(BiNode root) {
BiNode head = convertToCircular(root);
head.node1.node2 = null;
head.node1 = null;
return head;
}
public static void concat(BiNode x, BiNode y) {
x.node2 = y;
y.node1 = x;
}