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;
    }

你可能感兴趣的:(树)