LintCode二叉树&递归分治题总结

先来几个推荐的参考博客链接:

漫谈递归——递归的思想

非递归遍历二叉树总结

二叉树题总结

LintCode中二叉树与分治法那章有这么些题目:

《LintCode二叉树&递归分治题总结》

《LintCode二叉树&递归分治题总结》

376. Binary Tree Path Sum

要求等于target从根节点到叶子节点的路径和的组合,用DFS。

递归需要注意的是,终止情况是什么,也就是遇到叶子节点的时候返回,如果遇到叶子节点的时候恰好满足路径和等于target,就把这条路径加到总结果里面。

然后就是递归的主题,遇到左子节点、遇到右子节点的时候进入子递归。然后记得加入path路径后要remove掉,以方便保持当前路径的唯一性。

    public List<List<Integer>> binaryTreePathSum(TreeNode root, int target) {
        List<List<Integer>> res = new ArrayList();
        if (root == null) {
            return res;
        }
        ArrayList<Integer> path = new ArrayList<Integer>();
        path.add(root.val);
        dfs(res, path, root, target, root.val);
        return res;
    }
    private void dfs(List<List<Integer>> res, ArrayList<Integer> path, TreeNode root, int target, int sum) {
        // 遇到叶子节点的返回条件
        if (root.left == null && root.right == null) {
            if (sum == target) {
                res.add(new ArrayList<Integer>(path));
            }
            return;
        }
        
        if (root.left != null) {
            path.add(root.left.val);
            dfs(res, path, root.left, target, sum + root.left.val);
            path.remove(path.size() - 1);
        }
        
        if (root.right != null) {
            path.add(root.right.val);
            dfs(res, path, root.right, target, sum + root.right.val);
            path.remove(path.size() - 1);
        }
    }


469. Identical Binary Tree

判断两个二叉树是否是完全一样的,连结构也要一样。很明显考的是递归。既然是递归,就注意终结条件,

自然是对不同情况的判断,参数就2个,2个树的root。

1)2个root皆空,则是identical的

2)2个root只有一个空,则非identical

3)2个root皆不空,并且root值不等,则非identical

4)2个root皆不空,并且root值相等,则进行子递归判断左子树和右子树

    public boolean isIdentical(TreeNode a, TreeNode b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (a.val != b.val) {
            return false;
        }
        return isIdentical(a.left, b.left) && isIdentical(a.right, b.right);
    }

470. Tweaked Identical Binary Tree

也是判断2个二叉树是否完全一样,但是与上道题不同之处在于允许了对称扭曲。同样也是分4种情况判断:

    public boolean isTweakedIdentical(TreeNode a, TreeNode b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (a.val == b.val) {
            return (isTweakedIdentical(a.left, b.right) && isTweakedIdentical(b.left, a.right)) || (isTweakedIdentical(a.left, b.left) && isTweakedIdentical(b.right, a.right));
        }
        return false;
    }

468. Symmetric Binary Tree

判断一颗二叉树是不是对称的,跟上道题很像,只要把这颗树的左右子节点作为参数传进去就好。

    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (root.left == null && root.right == null) {
            return true;
        }
        return helper(root.left, root.right);
    }
    public boolean helper(TreeNode a, TreeNode b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (a.val == b.val) {
            return (helper(a.left, b.right) && helper(b.left, a.right));
        }
        return false;
    }

467. Complete Binary Tree

判断一棵树是不是完全二叉树。一个思路就是层次遍历,把每层的节点从左向右依此加入Stack,然后把Stack上层的None弹出,最后检查如果Stack中还有None说明不是Complete Tree 比如上面的不完全二叉树生成的数组为[1, 2, 3, None, 4, None, None],将右侧None弹出后为[1, 2, 3, None, 4],循环查找,发现还有None存在,所以是不完全二叉

    public boolean isComplete(TreeNode root) {
        if (root == null) {
            return true;
        }
        Queue<TreeNode> q = new LinkedList<TreeNode>();
        q.offer(root);
        boolean isNull = false;
        while (!q.isEmpty()) {
            TreeNode node = q.poll();
            if (node.left != null) {
                if (isNull == true) {
                    return false;
                }
                q.offer(node.left);
            } else {
                isNull = true;
            }
            
            if (node.right != null) {
                if (isNull == true) {
                    return false;
                }
                q.offer(node.right);
            } else {
                isNull = true;
            }
        }
        return true;
    }

还有另外一种方法是递归,比较难理解,可以参考这篇博文:http://www.geeksforgeeks.org/check-whether-binary-tree-complete-not-set-2-recursive-solution/

或者参考这篇博客:http://stackoverflow.com/questions/1442674/how-to-determine-whether-a-binary-tree-is-complete

97. Maximum Depth of Binary Tree

求一个二叉树的最大深度。

    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }

155. Minimum Depth of Binary Tree

求二叉树的最小深度,最小深度,就是从根节点到叶子节点最近的一条路的长度。不递归的话,可以用BFS层级遍历来做。

    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Queue<TreeNode> q = new LinkedList<TreeNode>();
        q.offer(root);
        int depth = 0;
        while (!q.isEmpty()) {
            int length = q.size();
            depth++;
            for (int i = 0; i < length; i++) {
                TreeNode n = q.poll();
                if (n.left == null && n.right == null) {
                    return depth;
                }
                if (n.left != null) {
                    q.offer(n.left);
                }
                if (n.right != null) {
                    q.offer(n.right);
                }
            }
            
        }
        return depth;
    }

也可以用递归来做,当当前节点是叶子节点时就返回结果。

    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        int l = (root.left == null) ? Integer.MAX_VALUE : minDepth(root.left); 
        int r = (root.right == null) ? Integer.MAX_VALUE : minDepth(root.right);
        return 1 + Math.min(l, r);
    }

93. Balanced Binary Tree

判断一棵树是否为平衡二叉树。如果左右子树的最大高度差超过1的话,那就不是平衡的了。所以这道题是基于求树的最大高度的。

一开始可能会以为直接对左子树和右子树求高度,然后比较两树的高度差。这样的想法是错误的,比如下面这个树就不是平衡的。

《LintCode二叉树&递归分治题总结》

所以在递归的过程中,对每一个节点进行高度平衡判断。只要中途有不平衡,那就是不平衡。如果左子树不平衡,那就是不平衡;如果右子树不平衡,那也是不平衡。

    private int depth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int l = depth(root.left);
        int r = depth(root.right);
        if (Math.abs(l-r) > 1 || l == -1 || r == -1) {
            return -1;
        }
        return 1 + Math.max(l, r);
    }
    public boolean isBalanced(TreeNode root) {
        return depth(root) != -1;
    }

88. Lowest Common Ancestor

求最近公共父节点。参数是1个root节点和2个输入节点A、B。求得就是A和B的最近公共父节点。

查找两个node的最早的公共祖先,分三种情况: 

1. 如果两个node在root的两边,那么最早的公共祖先就是root。 

2. 如果两个node在root的左边,那么把root.left作为root,再递归。 

3. 如果两个node在root的右边,那么把root.right作为root,再递归。

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) {
        if (root == null || root == A || root == B) {
            return root;
        }
        // divide
        TreeNode l = lowestCommonAncestor(root.left, A, B);
        TreeNode r = lowestCommonAncestor(root.right, A, B);
        
        // conquer
        if (l != null && r != null) {
            return root;
        }
        if (l != null) {
            return l;
        }
        if (r != null) {
            return r;
        }
        return null;
    }

474. Lowest Common Ancestor II

跟上道题类似,不过节点之间的关系是parent node的关系了。

如果每个结点都有一个指针指向它的父结点,于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表。因此这个问题转换为两个单向链表的第一个公共结点。

    public ParentTreeNode lowestCommonAncestorII(ParentTreeNode root,
                                                 ParentTreeNode A,
                                                 ParentTreeNode B) {
        Stack<ParentTreeNode> s1 = new Stack();
        Stack<ParentTreeNode> s2 = new Stack();
        s1.push(A);
        s2.push(B);
        while (s1.peek().parent != null) {
            s1.push(s1.peek().parent);
        }
        while (s2.peek().parent != null) {
            s2.push(s2.peek().parent);
        }
        if (s1.empty() || s2.empty()) {
            return root;
        }
        ParentTreeNode res = null;
        while (!s1.empty() && !s2.empty() && s1.peek() == s2.peek()) {
            res = s1.pop();
            s2.pop();
        }
        return res;
    }


85. Insert Node in a Binary Search Tree

如果是用非递归的方法,则用一个while循环,找到那个需要插入的地方的父节点。

    public TreeNode insertNode(TreeNode root, TreeNode node) {
        if (root == null) {
            return node;
        }
        
        TreeNode res = root;
        TreeNode father = null;
        while (root != null) {
            father = root;
            if (node.val > root.val) {
                root = root.right;
            } else {
                root = root.left;
            }
        }
        if (node.val > father.val) {
            father.right = node;
        } else {
            father.left = node;
        }
        return res;
    }

如果是用递归的话,那看起来就比较简单了:

    public TreeNode insertNode(TreeNode root, TreeNode node) {
        if (root == null) {
            return node;
        }
        if (node.val > root.val) {
            root.right = insertNode(root.right, node);
        } else {
            root.left = insertNode(root.left, node);
        }
        return root;
    }

87. Remove Node in Binary Search Tree

难度为Hard,Acceptance为25%.

首先,该题在《算法导论》一书中是有的。思路也较为容易理解,关键是实现算法时需要注意的各种细节。

那么下面说明该题的思路:

第一步,当然是找到要删除的节点node和它的父节点parent,这里采用普通的二分查找方法,找到了就返回node的父节点parent,找不到就返回null

第二步,就是删除该node节点,按照算法导论上的那个算法,删除node节点分为3种情况,1)node没有左右儿子,那么就直接把node的父节点指向node的儿子,即指向null。2)node只有一个儿子(左儿子或者右儿子),这个时候就直接把node的父节点指向左右儿子。3)node有2个儿子,那么这个时候就从左子树里面找到最小的那个节点,来代替node节点。

为了代码简洁,上述的3种情况可以简化为2种情况:

node没有左右儿子、node只有左儿子,可以合并为一种情况:即node的右子树不存在。此时,直接用parent节点的子节点指向该node节点的左节点。

node只有右儿子、node有左右2各儿子,可以合并为一种情况:即node的右子树存在。此时,需要找到node的右子树里的最小节点,用该节点替换node节点。

技巧:因为要删除的节点可能是根节点,因此为了算法的通用性,可以首先new一个dummy节点,该节点的左节点指向根节点,这样处理起来更为方便。

    public TreeNode removeNode(TreeNode root, int key) {
        TreeNode dummy = new TreeNode(0);
        dummy.left = root;


        TreeNode parent = find(dummy, root, key);
        TreeNode node = null;
        // node 不存在
        if (parent == null) {
            return root;
        }
        // node 在左边
        if (parent.left != null && parent.left.val == key) {
            node = parent.left;
        }
        // node 在右边
        if (parent.right != null && parent.right.val == key) {
            node = parent.right;
        }
        
        deleteNode(parent, node);
        return dummy.left;
    }
    private TreeNode find(TreeNode parent, TreeNode node, int key) {
        if(node == null) {
            return null;
        }
        if (key == node.val) {
            return parent;
        }
        if (key > node.val) {
            return find(node, node.right, key);
        } else {
            return find(node, node.left, key);
        }
    }
    private void deleteNode(TreeNode parent, TreeNode node) {
        // node的右子树不存在, 处理了两种情况,即只有左子树或者没有子树
        if (node.right == null) {
            if (parent.right == node) {
                parent.right = node.left;
            } else {
                parent.left = node.left;
            }
        } else {
            // node的右子树存在, 且左子树不存在
            if (node.left == null) {
                if (parent.right == node) {
                    parent.right = node.right;
                } else {
                    parent.left = node.right;
                }
            } else {
                // node的左右子树都存在,则寻找node右子树的最小节点,用于取代node
                TreeNode father = node;
                TreeNode t = node.right;
                
                while (t.left != null) {
                    father = t;
                    t = t.left;
                }
                if (father.left == t) {
                    father.left = t.right;
                } else {
                    father.right = t.right;
                }
                node.val = t.val;
            }
        }
    }

11. Search Range in Binary Search Tree

给定一个区间,要求在一个二叉查找树中,找到所有处在这个区间中的节点。最简单的思路就是先用dfs遍历,遍历过程中发现符合条件就加入ArrayList中。最后再对数组排序,返回。但是其实细细一想,是否可以免去这个排序的过程呢?是否可以在遍历的时候就能保持有序呢?考虑到二叉查找树的性质,如果用中序遍历,是可以保证这个的。

    public void dfs(TreeNode root, ArrayList<Integer> res, int k1, int k2) {
        if (root != null) {
            dfs(root.left, res, k1, k2);
            if (root.val >= k1 && root.val <= k2) {
                res.add(root.val);
            }
            dfs(root.right, res, k1, k2);
        }
    }
    public ArrayList<Integer> searchRange(TreeNode root, int k1, int k2) {
        ArrayList<Integer> res = new ArrayList();
        dfs(root, res, k1, k2);
        return res;
    }

66. Binary Tree Preorder Traversal

67. Binary Tree Inorder Traversal

68. Binary Tree Postorder Traversal

二叉树的前中后序遍历,递归的非常简单,就不再赘述了,非递归的可以参考这篇博客:http://zisong.me/post/suan-fa/geng-jian-dan-de-bian-li-er-cha-shu-de-fang-fa

69. Binary Tree Level Order Traversal

二叉树层次遍历,这个算法已经烂大街了,就不多说了

    public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) {
        ArrayList<ArrayList<Integer>> res = new ArrayList();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        
        while(!queue.isEmpty()) {
            ArrayList<Integer> level = new ArrayList();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode tmp = queue.poll();
                level.add(tmp.val);
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
            res.add(level);
        }
        return res;
    }

70. Binary Tree Level Order Traversal II

也是二叉树的层次遍历,不过是从叶子节点到根节点了,跟上道题类似,把最后的数组reverse一下就得到了。

就在上面的代码的最后加上这么一句就行:

Collections.reverse(res);

71. Binary Tree Zigzag Level Order Traversal

“之”字型的层次遍历,加个变量判断一下奇偶数就好。

    public ArrayList<ArrayList<Integer>> zigzagLevelOrder(TreeNode root) {
        ArrayList<ArrayList<Integer>> res = new ArrayList();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        
        int level = 1;
        while(!queue.isEmpty()) {
            ArrayList<Integer> path = new ArrayList();
            int size = queue.size();
            level++;
            for (int i = 0; i < size; i++) {
                TreeNode tmp = queue.poll();
                path.add(tmp.val);
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
            if (level % 2 == 1) {
                Collections.reverse(path);
            }
            res.add(path);
        }
        return res;
    }

72. Construct Binary Tree from Inorder and Postorder Traversal

根据中序遍历和后续遍历结果,构建出那颗二叉树。要知道如何构建二叉树,首先我们需要知道二叉树的几种遍历方式,譬如有如下的二叉树:

                1
        --------|-------
        2               3
    ----|----       ----|----
    4       5       6       7

前序遍历 1245367 

中序遍历 4251637 

后续遍历 4526731

仍然以上面那棵二叉树为例,我们可以发现,对于后序遍历来说,最后一个元素一定是根节点,也就是1。然后我们在中序遍历的结果里面找到1所在的位置,那么它的左半部分就是其左子树,有半部分就是其右子树。

我们将中序遍历左半部分425取出,同时发现后序遍历的结果也在相应的位置上面,只是顺序稍微不一样,也就是452。我们可以发现,后序遍历中的2就是该子树的根节点。

上面说到了左子树,对于右子树,我们取出637,同时发现后序遍历中对应的数据偏移了一格,并且顺序也不一样,为673。而3就是这颗右子树的根节点。

重复上述过程,通过后续遍历找到根节点,然后在中序遍历数据中根据根节点拆分成两个部分,同时将对应的后序遍历的数据也拆分成两个部分,重复递归,就可以得到整个二叉树了。

    private TreeNode build(int[] in, int instart, int inend, int[] post, int poststart, int postend) {
        if (instart > inend) {
            return null;
        }
        TreeNode root = new TreeNode(post[postend]);
        int pos = -1;
        for (int i = 0; i <= inend; i++) {
            if (in[i] == post[postend]) {
                pos = i;
            }
        }
        root.left = build(in, instart, pos - 1, post, poststart, poststart + pos - instart - 1);
        root.right = build(in, pos + 1, inend, post, poststart + pos - instart, postend - 1);
        
        return root;
    }
    public TreeNode buildTree(int[] in, int[] post) {
        return build(in, 0, in.length - 1, post, 0, post.length - 1);
    }

73. Construct Binary Tree from Preorder and Inorder Traversal

根据后续和中序遍历结果,构建出那颗二叉树。参考:https://siddontang.gitbooks.io/leetcode-solution/content/tree/construct_binary_tree.html

    private TreeNode build(int[] pre, int prestart, int preend, int[] in, int instart, int inend) {
        if (instart > inend) {
            return null;
        }
        TreeNode root = new TreeNode(pre[prestart]);
        int pos = -1;
        for (int i = 0; i <= inend; i++) {
            if (in[i] == pre[prestart]) {
                pos = i;
            }
        }
        root.left = build(pre, prestart + 1, prestart + pos - instart, in, instart, pos - 1);
        root.right = build(pre, prestart + pos - instart + 1, preend, in, pos + 1, inend);
        
        return root;
    }
    public TreeNode buildTree(int[] pre, int[] in) {
        return build(pre, 0, pre.length - 1, in, 0, in.length - 1);
    }

7. Binary Tree Serialization

可以把二叉树序列化String,也可以把String版本的二叉树还原成节点版本的二叉树。可参见这篇博客:http://blog.csdn.net/ljiabin/article/details/49474445

《LintCode二叉树&递归分治题总结》

    public String serialize(TreeNode root) {
        String res = "";
        if (root == null) {
            return res;
        }
        Queue<TreeNode> q = new LinkedList<TreeNode>();
        q.offer(root);
        res += root.val;
        res += ',';
        while (!q.isEmpty()) {
            int length = q.size();
            for (int i = 0; i < length; i++) {
                TreeNode node = q.poll();
                if (node.left != null) {
                    q.offer(node.left);
                    res += node.left.val;
                    res += ',';
                } else {
                    res += "#,";
                }
                if (node.right != null) {
                    q.offer(node.right);
                    res += node.right.val;
                    res += ',';
                } else {
                    res += "#,";
                }
            }
        }
        return res;
    }
    
    public TreeNode deserialize(String data) {
        if (data == null || data == "") {
            return null;
        }
        String[] res = data.split(",");
        // 节点i之前null节点的个数
        int[] nums = new int[res.length];
        TreeNode[] arr = new TreeNode[res.length];

        for (int i = 0; i < res.length; i++) {
            if (i > 0) {
                nums[i] = nums[i - 1];
            }
            if (res[i].equals("#")) {
                arr[i] = null;
                nums[i]++;
            } else {
                arr[i] = new TreeNode(Integer.parseInt(res[i]));
            }
        }
        for (int i = 0; i < res.length; i++) {
            if (arr[i] != null) {
                arr[i].left = arr[(i - nums[i]) * 2 + 1];
                arr[i].right = arr[(i - nums[i]) * 2 + 2];
            }
        }
        return arr[0];
    }


475. Binary Tree Maximum Path Sum II

找到一个二叉树从root到叶子节点的最大的一个路径和,非常简单的递归,但是需要考虑的问题是,节点可能是负数。所以每次返回的时候,是选择(左节点,右节点,0)的最大值:

    public int maxPathSum2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return root.val + Math.max(0, Math.max(maxPathSum2(root.left), maxPathSum2(root.right)));
    }

94. Binary Tree Maximum Path Sum

求一个二叉树的最大路径和,可以从任意节点到任意节点。

最优路径上的节点一定是连续的,不能中断

最优路径中一定包含某个子树的根节点

写一个递归函数,实现计算根节点到任意点的最大路径和,以及穿过根节点的最大路径和,用一个全局变量保存最优解。

需要注意的是,节点可能会包含负数,所以计算的时候,要判断一下负数。

    private int res = Integer.MIN_VALUE;
    private int helper(TreeNode root) {
        if (root == null) {
            return Integer.MIN_VALUE;
        }
        // divide
        int left = helper(root.left);
        int right = helper(root.right);
        // conquer
        int root2any = Math.max(Math.max(left, right), 0) + root.val;
        int any2any = Math.max(0, left) + Math.max(0, right) + root.val;
        res = Math.max(res, Math.max(root2any, any2any));
        return root2any;
    }
    public int maxPathSum(TreeNode root) {
        if (root == null) {
            return Integer.MIN_VALUE;
        }
        helper(root);
        return res;
    }

448. Inorder Successor in Binary Search Tree

找到一个节点的中序后继节点。我的思路是先通过中序遍历得到一个有序的数组,然后再在这个数组中二分查找,找到那个节点。这个算法比标准答案还要快些

    private void inorder(TreeNode root, ArrayList<TreeNode> res) {
        if (root != null) {
            inorder(root.left, res);
            res.add(root);
            inorder(root.right, res);
        }
    }
    private int binarySearch(ArrayList<TreeNode> res, int val) {
        int start = 0, end = res.size() - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (res.get(mid).val == val) {
                if (mid + 1 < res.size()) {
                    return mid + 1;
                } else {
                    return -1;
                }
            } else if (val > res.get(mid).val) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (res.get(start).val == val && start + 1 < res.size()) {
            return start + 1;
        }
        if (res.get(end).val == val && end + 1 < res.size()) {
            return end + 1;
        }
        return -1;
    }
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        ArrayList<TreeNode> res = new ArrayList();
        if (root == null || p == null) {
            return null;
        }
        inorder(root, res);
        int pos = binarySearch(res, p.val);
        if (pos != -1) {
            return res.get(pos);
        }
        return null;
    }

当然还有种方法,也是在CC150上看到的,伪代码如下:

Node inorderSucc(Node n) {
    if (n has a right subtree) {
        return leftmost child of right subtree
    } else {
        while ( n is a right son of n.parent) {
            n = n.parent;
        }
        return n.parent;
    }
}

由于题目未提供指向父亲的指针,所以这种情况需要从根往节点n进行搜索了: 把从根往Node n一路搜索遇到的节点都存入stack,然后再逐个从stack里往外pop,遇到的第一个比Node n的value大的值便是答案了。如果一路pop到root还没有的话,那就返回null:

class Solution {
    public TreeNode leftMost(TreeNode root) {
        while (root.left != null) {
            root = root.left;
        }
        return root;
    }
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null || p == null) {
            return null;
        }
        if (p.right != null) {
            return leftMost(p.right);
        } else {
            Stack<TreeNode> stack = new Stack<TreeNode>();
            stack.push(root);
            while (root.val != p.val) {
                if (p.val > root.val) {
                    stack.push(root.right);
                    root = root.right;
                } else if (p.val < root.val) {
                    stack.push(root.left);
                    root = root.left;
                } else {
                    break;
                }
            }
            while (!stack.isEmpty()) {
                TreeNode head = stack.pop();
                if (head.val > p.val) {
                    return head;
                }
            }
        }
        return null;
    }
}

95. Validate Binary Search Tree

判断一颗二叉树是不是合法的二分查找树,这时可以用到一个性质,如果它的中序遍历的结果是非递减的,拿它就是合法的:

    private void inorder(TreeNode root, ArrayList<Integer> list) {
        if (root != null) {
            inorder(root.left, list);
            list.add(root.val);
            inorder(root.right, list);
        }
    }
    public boolean isValidBST(TreeNode root) {
        ArrayList<Integer> list = new ArrayList();
        inorder(root, list);
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i) <= list.get(i - 1)) {
                return false;
            }
        }
        return true;
    }

当然我们可以优化一下,在遍历的过程中就比较两个相邻的数是否满足递增序列的要求,这样就能把空间复杂度降低:

    public Integer last = null;
    public boolean inorder(TreeNode root) {
        if (root == null) {
            return true;
        }
        boolean l = inorder(root.left);
        if (last != null && root.val <= last) {
            return false;
        }
        last = root.val;
        boolean r = inorder(root.right);
        return l && r;
    }

还有一种方法是min max方法,我在CC150里看到的:

    public boolean checkBST(TreeNode root, Integer min, Integer max) {
        if (root == null) {
            return true;
        }
        if (max != null && root.val >= max || min != null && root.val <= min) {
            return false;
        }
        boolean l = checkBST(root.left, min, root.val);
        boolean r = checkBST(root.right, root.val, max);
        return l && r;
    }

86. Binary Search Tree Iterator

算是一道系统设计题,给定一些函数接口,要你按照要求实现。实现一个iterator可以遍历二叉树。我用的是中序遍历,把整个树保存在一个ArrayList中,然后用一个pos变量记录当前位置。比九章的标准答案还要快一点:

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 * Example of iterate a tree:
 * BSTIterator iterator = new BSTIterator(root);
 * while (iterator.hasNext()) {
 *    TreeNode node = iterator.next();
 *    do something for node
 * } 
 */
public class BSTIterator {
    private ArrayList<TreeNode> arr = new ArrayList<TreeNode>();
    private int pos = -1;
    
    //@param root: The root of binary tree.
    private void inorder(TreeNode root) {
        if (root != null) {
            inorder(root.left);
            arr.add(root);
            inorder(root.right);
        }
    }
    public BSTIterator(TreeNode root) {
        inorder(root);
        if (arr.size() > 0) {
            pos = 0;
        }
    }


    //@return: True if there has next node, or false
    public boolean hasNext() {
        return pos >= 0 && pos < arr.size();
    }
    
    //@return: return next node
    public TreeNode next() {
        return arr.get(pos++);
    }
}

此外,Chapter之外的二叉树还有这么一些题目:

175. Invert Binary Tree

反转二叉树,HomeBrew的创始人就是倒在了这道题下,用递归做很简单,先把根节点的左右子树对调,然后再递归得对左右子树调用递归。递归返回条件就是当前节点为空。

    public void invertBinaryTree(TreeNode root) {
        if (root == null) {
            return;
        }
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        
        invertBinaryTree(root.left);
        invertBinaryTree(root.right);
    }

480. Binary Tree Paths

要求返回所有从根节点到叶子节点的路径。这种要求所有路径的,一看就基本知道八九成是用DFS来做了:

    private void dfs(List<String> res, String path, TreeNode root) {
        if (root.left == null && root.right == null) {
            path += root.val;
            res.add(path);
            return;
        }
        if (root.left != null) {
            dfs(res, path + root.val + "->", root.left);
        }
        if (root.right != null) {
            dfs(res, path + root.val + "->", root.right);
        }
    }
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList();
        if (root == null) {
            return res;
        }
        dfs(res, "", root);
        return res;
    }

245. Subtree

判断一棵树A是否包含另一棵树B,用DFS遍历A的每个节点,对于A的每个节点,调用一个equal函数判断A的这个节点的子树和B树是否完全相等。

    
    private void dfs(TreeNode T1, TreeNode T2) {
        if (T1 != null) {
            if (equal(T1, T2)) {
                flag = true;
                return;
            }
            dfs(T1.left, T2);
            dfs(T1.right, T2);
        }
    }
    public boolean isSubtree(TreeNode T1, TreeNode T2) {
        if (T2 == null) {
            return true;
        }
        dfs(T1, T2);
        return flag;
    }
    private boolean equal(TreeNode T1, TreeNode T2) {
        // 两个都为空
        if (T1 == null && T2 == null) {
            return true;
        }
        // 只有一个为空
        if (T1 == null || T2 == null) {
            return false;
        }
        // 都不空,且不相等的时候,就返回false
        if (T1.val != T2.val) {
            return false;
        }
        // 相等,则进一步判断
        return equal(T1.left, T2.left) && equal(T1.right, T2.right);
    }

453. Flatten Binary Tree to Linked List

要求把一棵树按照先根遍历的规则,转换为一个只有右子树的数:

              1
               \
     1          2
    / \          \
   2   5    =>    3
  / \   \          \
 3   4   6          4
                     \
                      5
                       \
                        6
    private TreeNode p = null;
    private void inorder(TreeNode root) {
        if (root != null) {
            if (p != null) {
                p.right = root;
                p.left = null; <span style="font-family: Arial, Helvetica, sans-serif;">// 这一行代码不能少!</span>
            }
            p = root;
            TreeNode r = root.right; // 这一行代码不能少!
            
            inorder(root.left);
            inorder(r);
        }
    }
    public void flatten(TreeNode root) {
        inorder(root);
    }

注意每次遍历是把上个节点和当前节点建立联系。如果上个节点存在,则直接把上个节点的右子树指向当前节点。然后把p指向当前节点,并把右子树先拿出来(这点很重要,不拿出来基本就是stackoverflow了),再对左右子树分别进行子递归。

177. Convert Sorted Array to Binary Search Tree With Minimal Height

把一个有序数组转换为一颗高度最小的二叉搜索树。思路很简单,递归建树,每次递归都选择数组的中点建树。

    private TreeNode build(int[] arr, int start, int end, TreeNode root) {
        if (start <= end) {
            int mid = start + (end - start) / 2;
            root = new TreeNode(arr[mid]);
            root.left = build(arr, start, mid - 1, root.left);
            root.right = build(arr, mid + 1, end, root.right);
        }
        return root;
    }
    public TreeNode sortedArrayToBST(int[] A) {  
        TreeNode root = null;
        root = build(A, 0, A.length - 1, root);
        return root;
    } 

140. Fast Power

Calculate the an % b where a, b and n are all 32bit integers.

For 231 % 3 = 2

For 1001000 % 1000 = 0

首先可以参考下百度百科里对于取模运算的定义:http://baike.baidu.com/view/4887065.htm

我们发现,取模运算有这么些规律:

(a * b) % p = (a % p * b % p) % p

《LintCode二叉树&递归分治题总结》

    public int fastPower(int a, int b, int n) {
        if (n == 1) {
            return a % b;
        }
        if (n == 0) {
            return 1 % b;
        }
        long res = fastPower(a, b, n / 2);
        res = res * res % b;
        if (n % 2 == 1) {
            res = res * a % b % b;
        }
        return (int)res;
    }

428. Pow(x, n)

这道题与上面那道题有异曲同工之妙,不过推导公式没有上面那么复杂了。也是分治法,需要考虑n小于0的情况,还有分奇偶的情况。如果用分治法则时间复杂度是O(logn),如果是直接用从n递归到n-1再递归到n-2···这种方法,则时间复杂度是O(N)

    public double myPow(double x, int n) {
        if (n == 0) {
            return 1.0;
        }
        if (n < 0) {
            return 1.0 / myPow(x, -1 * n);
        }
        double half = myPow(x, n / 2);
        if (n % 2 == 1) {
            return x * half * half;
        }
        return half * half;
    }

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/luoshengkim/article/details/52040827
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞