Saturday, February 28, 2015

Binary Tree Zigzag Level Order Traversal

Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).
For example:
Given binary tree {3,9,20,#,#,15,7},
    3
   / \
  9  20
    /  \
   15   7
return its zigzag level order traversal as:
[
  [3],
  [20,9],
  [15,7]
]
Solution:
在遍历的时候偶数层自左向右,而奇数层自右向左。Binary Tree Level Order Traversal中我们是维护了一个队列来完成遍历,而在这里为了使每次都倒序出来,我们很容易想到用栈的结构来完成这个操作。有一个区别是这里我们需要一层一层的来处理(原来可以按队列插入就可以,因为后进来的元素不会先处理),所以会同时维护新旧两个栈,一个来读取,一个存储下一层结点。总体来说还是一次遍历完成,所以时间复杂度是O(n),空间复杂度最坏是两层的结点,所以数量级还是O(n)(满二叉树最后一层的结点是n/2个)。
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ArrayList> zigzagLevelOrder(TreeNode root) {
        ArrayList> res = new ArrayList>();
        if(root == null)
            return res;
        
        ArrayList list = new ArrayList();
        LinkedList stack = new LinkedList();
        list.add(root.val);
        res.add(list);
        stack.push(root);
        int level = 1;
        
        while(!stack.isEmpty()) {
            list = new ArrayList();
            LinkedList newStack = new LinkedList();  //for storing nodes which will be used for next level
            while(!stack.isEmpty()) {
                TreeNode node = stack.pop();
                if(level % 2 == 0) {  //偶数层从左向右
                    if(node.left != null) {
                        newStack.push(node.left);
                        list.add(node.left.val);
                    }
                    if(node.right != null) {
                        newStack.push(node.right);
                        list.add(node.right.val);
                    }
                }else {  //奇数层从右向左
                    if(node.right != null) {
                        newStack.push(node.right);
                        list.add(node.right.val);
                    }
                    if(node.left != null) {
                        newStack.push(node.left);
                        list.add(node.left.val);
                    }
                }
            }
            level++;
            if(list.size() > 0) {
                res.add(list);
            }
            stack = newStack; //assign the newStack to stack, it will be used in next loop
        }
        return res;
    }
}

Longest Substring Without Repeating Characters

Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.
Note:

Solution1:
维护一个窗口,每次关注窗口中的字符串,在每次判断中,左窗口和右窗口选择其一向前移动。同样是维护一个HashSet, 正常情况下移动右窗口,如果没有出现重复则继续移动右窗口,如果发现重复字符,则说明当前窗口中的串已经不满足要求,继续移动有窗口不可能得到更好的结果,此时移动左窗口,直到不再有重复字符为止,中间跳过的这些串中不会有更好的结果,因为他们不是重复就是更短。
因为左窗口和右窗口都只向前,所以两个窗口都对每个元素访问不超过一遍,因此时间复杂度为O(2*n)=O(n),是线性算法。空间复杂度为HashSet的size,也是O(n)
public class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0)
            return 0;
        
        HashSet set = new HashSet();
        int left = 0;
        int right = 0;
        int max = 0;
        while(right < s.length()) {
            if(set.contains(s.charAt(right))) {
                if(max < right - left) {
                    max = right - left;
                }
                
                while(s.charAt(left) != s.charAt(right)) {
                    set.remove(s.charAt(left));
                    left++;
                }
                left++;
            }else {
                set.add(s.charAt(right));
            }
            right++;
        }
        
        max = Math.max(max, right - left);
        return max;
    }
}

Solution2:
O(n) runtime, O(1) space – Two iterations: Need two indices to record the head and the tail of the current substring. Since i and j both traverse at most n steps, the worst case would be 2n steps, which the runtime complexity must be O(n). Note that the space complexity is constant O(1), even though we are allocating an array. This is because no matter how long the string is, the size of the array stays the same at 256.
public class Solution {
    public int lengthOfLongestSubstring(String s) {
        boolean[] exist = new boolean[256];
        int i = 0, maxLen = 0;
        for(int j = 0; j < s.length(); j++) {
            while(exist[s.charAt(j)]) {
                exist[s.charAt(i)] = false;
                i++;
            }
            exist[s.charAt(j)] = true;
            maxLen = Math.max(maxLen, j - i + 1);
        }
        return maxLen;
    }
}
Solution3:
O(n) runtime, O(1) space – Single iteration: Instead of using a table to tell if a character exists or not, we could define a mapping of the characters to its index. Then we can skip the characters immediately when we found a repeated character.
public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] charMap = new int[256];
        Arrays.fill(charMap, -1);  //Assigns -1 to each element of the specified array of ints
        int i = 0, maxLen = 0;
        for(int j = 0; j < s.length(); j++) {
            if(charMap[s.charAt(j)] >= i) {
                i = charMap[s.charAt(j)] + 1;
            }
            charMap[s.charAt(j)] = j;
            maxLen = Math.max(maxLen, j - i + 1);
        }
        return maxLen;
    }
}

Construct Binary Tree from Preorder and Inorder Traversal

Given preorder and inorder traversal of a tree, construct the binary tree.
Note:
You may assume that duplicates do not exist in the tree.
Solution: recursion
假设树的先序遍历是12453687,中序遍历是42516837。这里最重要的一点就是先序遍历可以提供根的所在,而根据中序遍历的性质知道根的所在就可以将序列分为左右子树。比如上述例子,我们知道1是根,所以根据中序遍历的结果425是左子树,而6837就是右子树。接下来根据切出来的左右子树的长度又可以在先序便利中确定左右子树对应的子序列(先序遍历也是先左子树后右子树)。根据这个流程,左子树的先序遍历和中序遍历分别是245和425,右子树的先序遍历和中序遍历则是3687和6837,我们重复以上方法,可以继续找到根和左右子树,直到剩下一个元素。可以看出这是一个比较明显的递归过程,对于寻找根所对应的下标,我们可以先建立一个HashMap,以免后面需要进行线行搜索,这样每次递归中就只需要常量操作就可以完成对根的确定和左右子树的分割。
算法最终相当于一次树的遍历,每个结点只会被访问一次,所以时间复杂度是O(n)。而空间我们需要建立一个map来存储元素到下标的映射,所以空间复杂度是O(n)
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || preorder.length == 0 || inorder == null || inorder.length == 0)
            return null;
        
        HashMap map = new HashMap();
        for(int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        
        return helper(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1, map);
    }
    
    public TreeNode helper(int[] preorder, int preL, int preR, int[] inorder, int inL, int inR, HashMap map) {
        if(preL > preR || inL > inR)
            return null;
        
        TreeNode root = new TreeNode(preorder[preL]);
        int index = map.get(root.val);
        root.left = helper(preorder, preL+1, preL+index-inL, inorder, inL, index-1, map);
        root.right = helper(preorder, preL+(index-inL)+1, preR, inorder, index+1, inR, map);
        return root;
    }
}

Construct Binary Tree from Inorder and Postorder Traversal

Given inorder and postorder traversal of a tree, construct the binary tree.
Note:
You may assume that duplicates do not exist in the tree.
Solution: recursion
这道题和Construct Binary Tree from Preorder and Inorder Traversal思路完全一样
这里的区别是要从中序遍历和后序遍历中构造出树,算法还是一样,只是现在取根是从后面取(因为后序遍历根是遍历的最后一个元素)。将inorder[]放进hashmap里,然后根据根的值从inorder中取得对应的index。
时间复杂度和空间复杂度也还是O(n)
( 根据preorder和postorder不能重新构造出树来, 不能唯一确定一棵树)
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder == null || postorder == null)
            return null;
        HashMap map = new HashMap();
        for(int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        return helper(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1, map);
    }
    
    public TreeNode helper(int[] inorder, int inL, int inR, int[] postorder, int posL, int posR, HashMap map){
        if(inL > inR || posL > posR)
            return null;
        
        TreeNode root = new TreeNode(postorder[posR]);
        int index = map.get(root.val);
        root.left = helper(inorder, inL, index-1, postorder, posL, posL+index-inL-1, map);
        root.right = helper(inorder, index+1, inR, postorder, posR-(inR-index), posR-1, map);
        return root;
    }
}

Largest Number

Given a list of non negative integers, arrange them such that they form the largest number.
For example, given [3, 30, 34, 5, 9], the largest formed number is 9534330.
Note: The result may be very large, so you need to return a string instead of an integer.
Solution:
对于两个备选数字a和b,如果str(a) + str(b) > str(b) + str(a),则a在b之前,否则b在a之前
按照此原则对原数组从大到小排序即可。 Collections.sort() guaranteed the nlogn performance, 所以时间复杂度O(nlogn)
易错样例:
Input:     [0,0]
Output:    "00"
Expected:  "0"

public class Solution {
    public String largestNumber(int[] num) {
        if(num == null || num.length == 0)
            return null;
        
        ArrayList list = new ArrayList();
        for(int n : num) {
            list.add(n);
        }
        
        //define a comparator first to sort the integer
        Comparator comp = new Comparator() {
            @Override
            public int compare(Integer n1, Integer n2) {
                String s1 = "" + n1 + n2;
                String s2 = "" + n2 + n1;
                return s2.compareTo(s1);
                //condition1: if s1 < s2, 
                //s2.compareTo(s1) will return positive number which is opposite with original order,
                //so descending order.
                //condition2: if s1 > s2,
                //s2.compareTo(s1) will return negative number which is opposite with original order,
                //so still descending order.
                
            }
        };
        //Sorts the list according to the order induced by the comp.
        Collections.sort(list, comp);
        
        StringBuilder str = new StringBuilder();
        for(int n : list) {
            str.append(n);
        }
        
        //if string is 0000..., only return one 0
        if(str.charAt(0) == '0')
            return "0";
        
        return str.toString();
    }
}
=========================================================

Collections.sort()

public static <T> void sort(List<T> list,
                            Comparator<? super T> c)
Sorts the specified list according to the order induced by the specified comparator. All elements in the list must be mutually comparable using the specified comparator (that is,c.compare(e1, e2) must not throw a ClassCastException for any elements e1 and e2 in the list).This sort is guaranteed to be stable: equal elements will not be reordered as a result of the sort.
The sorting algorithm is a modified mergesort (in which the merge is omitted if the highest element in the low sublist is less than the lowest element in the high sublist). This algorithm offers guaranteed n log(n) performance. The specified list must be modifiable, but need not be resizable. This implementation dumps the specified list into an array, sorts the array, and iterates over the list resetting each element from the corresponding position in the array. This avoids the n2 log(n) performance that would result from attempting to sort a linked list in place.
Parameters:
list - the list to be sorted.
c - the comparator to determine the order of the list. A null value indicates that the elements' natural ordering should be used.
CompareTo()
The method compareTo() is used for comparing two strings lexicographically. Each character of both the strings is converted into a Unicode value for comparison. 
If both the strings are equal then this method returns 0 else it returns positive or negative value. 
The result is positive if the first string is lexicographically greater than the second string else the result would be negative.

Convert Sorted Array to Binary Search Tree

Given an array where elements are sorted in ascending order, convert it to a height balanced BST.
Solution: recursion
把一个数组看成一棵树(也就是以中点为根,左右为左右子树,依次下去)数组就等价于一个二分查找树。
把中间元素转化为根,然后递归构造左右子树。用二叉树递归的方法来实现,以根作为返回值,每层递归函数取中间元素,作为当前根和赋上结点值,然后左右结点接上左右区间的递归函数返回值。
时间复杂度还是一次树遍历O(n),总的空间复杂度是栈空间O(logn), stack space.
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode sortedArrayToBST(int[] num) {
        if(num == null || num.length == 0)
            return null;
        
        return sortHelper(num, 0, num.length - 1);
    }
    
    public TreeNode sortHelper(int[] num, int start, int end) {
        if(start > end)
            return null;
        
        int mid = (start + end) / 2;
        TreeNode root = new TreeNode(num[mid]);
        root.left = sortHelper(num, start, mid - 1);
        root.right = sortHelper(num, mid + 1, end);
        return root;
    }
}

Friday, February 27, 2015

Convert Sorted List to Binary Search Tree

Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.
Note:
Things get a little more complicated when you have a singly linked list instead of an array. Please note that in linked list, you no longer have random access to an element in O(1) time.
Naive Solution:
A naive way is to apply the previous solution directly. In each recursive call, you would have to traverse half of the list’s length to find the middle element. The run time complexity is clearly O(N lg N), where N is the total number of elements in the list. This is because each level of recursive call requires a total of N/2 traversal steps in the list, and there are a total of lg N number of levels (ie, the height of the balanced tree).
Best Solution:
We no longer create nodes in the tree using the top-down approach. We create nodes bottom-up, and assign them to its parents. The bottom-up approach enables us to access the list in its order while creating nodes.
The algorithm requires the list’s length to be passed in as the function’s parameters. 
对于一个链表我们是不能常量时间访问它的中间元素的。这时候就要利用到树的中序遍历了,按照递归中序遍历的顺序对链表结点一个个进行访问,而我们要构造的二分查找树正是按照链表的顺序来的。思路就是先对左子树进行递归,然后将当前结点作为根,迭代到下一个链表结点,最后在递归求出右子树即可。
The list’s length could be found in O(N) time by traversing the entire list’s once. The recursive calls traverse the list and create tree’s nodes by the list’s order, which also takes O(N) time. Therefore, the overall run time complexity is still O(N). 空间复杂度是栈空间O(logn) .
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; next = null; }
 * }
 */
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    
    public TreeNode sortedListToBST(ListNode head) {
        if(head == null)
            return null;
        
        ListNode cur = head;
        int count = 0;
        while(cur != null) {
            cur = cur.next;
            count++;
        }
        
        ArrayList list = new ArrayList();
        list.add(head);
        return sortHelper(0, count - 1, list);
    }
    
    // build bottom-up
    public TreeNode sortHelper(int start, int end, ArrayList list) {
        if(start > end)  //if finished, get the root
            return null;
        
        int mid = (start + end) / 2; //find the mid element
        
        TreeNode left = sortHelper(start, mid - 1, list);  //build left subtree
        TreeNode root = new TreeNode(list.get(0).val); //build root node
        root.left = left;  //assign left to the left subtree
        list.set(0,list.get(0).next);  //pointer +1
        TreeNode right = sortHelper(mid + 1, end, list);  //move to next node to build right subtree
        root.right = right;  //assign right to the right subtree
        return root;
    }
}

Thursday, February 26, 2015

Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.
For example:
Given the below binary tree and sum = 22,
              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
return
[
   [5,4,11,2],
   [5,8,4,5]
]


Solution: recursion
跟Path Sum的要求很接近,寻找从根到叶子的路径。
要求是求所有满足条件的路径,所以需要数据结构来维护中间路径结果以及保存满足条件的所有路径。
这里的时间是一次遍历O(n),而空间复杂度则取决于满足条件的路径和的数量(假设是k条),则空间是O(klogn)。
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ArrayList> pathSum(TreeNode root, int sum) {
        ArrayList> res = new ArrayList>();
        if(root == null)
            return res;
        
        ArrayList item = new ArrayList();
        item.add(root.val);
        helper(root, sum - root.val, item, res);
        return res;
    }
    
    public void helper(TreeNode root, int sum, ArrayList item, ArrayList> res) {
        if(root == null)
            return;
        
        if(root.left == null && root.right == null && sum == 0) {
            res.add(new ArrayList (item));
            return;
        }
        
        if(root.left != null) {
            item.add(root.left.val);
            helper(root.left, sum - root.left.val, item, res);
            item.remove(item.size() - 1);
        }
        
        if(root.right != null) {
            item.add(root.right.val);
            helper(root.right, sum - root.right.val, item, res);
            item.remove(item.size() - 1);
        }
    }
}

Reference: http://blog.csdn.net/linhuanmars/article/details/23655413

Flatten Binary Tree to Linked List

Given a binary tree, flatten it to a linked list in-place.
For example,
Given
         1
        / \
       2   5
      / \   \
     3   4   6
The flattened tree should look like:
   1
    \
     2
      \
       3
        \
         4
          \
           5
            \
             6
Hints:
If you notice carefully in the flattened tree, each node's right child points to the next node of a pre-order traversal.
Solution1: iteration
只要树中有多出来的分叉(左子树),就嫁接到根节点和右子树之间.
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void flatten(TreeNode root) {
        while(root != null) {
            if(root.left != null) {
                TreeNode p = root.left;
                while(p.right != null) {
                    p = p.right;
                }
                p.right = root.right;
                root.right = root.left;
                root.left = null;
            }
            root = root.right;
        }
    }
}

Solution2: recursion
用递归来解决,维护先序遍历的前一个结点pre,然后每次把pre的左结点置空,右结点设为当前结点。这里需要注意的一个问题就是我们要先把右子结点保存一下,以便等会可以进行递归,否则有可能当前结点的右结点会被覆盖,后面就取不到了。
算法的复杂度时间上还是一次遍历,O(n)。空间上是栈的大小,O(logn)
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void flatten(TreeNode root) {
       ArrayList pre = new ArrayList();
       pre.add(null);
       helper(root, pre);
    }
    
    public void helper(TreeNode root, ArrayList pre) {
        if(root == null)
            return;
        
        TreeNode right = root.right; //save right child first in case it will be replace by others
        if(pre.get(0) != null) {
            pre.get(0).left = null;
            pre.get(0).right = root;
        }
        
        pre.set(0,root);
        helper(root.left, pre);
        helper(right, pre);
    }
}

Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
For example, given n = 3, a solution set is:
"((()))", "(()())", "(())()", "()(())", "()()()"
Note:
Solution: Greedy and Recursive
卡特兰数
这道题其实是关于卡特兰数的,如果只是要输出结果有多少组,那么直接用卡特兰数的公式就可以。特别是其中
这个递推式的定义,很多这类问题都可以归结成这个表达式。这个题对于C的定义就是第一对括号中包含有几组括号。因为第一组括号中包含的括号对数量都不同,所以不会重复,接下来就是一个递归定义,里面又可以继续用更小的C去求组合可能性。
递归的方法,因为可以归结为子问题去操作。在每次递归函数中记录左括号和右括号的剩余数量,然后有两种选择,一个是放一个左括号,另一种是放一个右括号。当然有一些否定条件,比如剩余的右括号不能比左括号少,或者左括号右括号数量都要大于0。正常结束条件是左右括号数量都为0。算法的复杂度是O(结果的数量),因为卡特兰数并不是一个多项式量级的数字,所以算法也不是多项式复杂度的。
Take advantage of the feature of parentheses, left side and right side must match each other,  in other words, if there a left side parenthese,  whatever position it is, there must be a right side parenthese to match it.
public class Solution {
    public List generateParenthesis(int n) {
        List result = new ArrayList();
        if(n < 1)
            return result;
            
        int left = n;
        int right = n;
        String current = new String();
        generate(left, right, current, result);
        return result;
    }
    
    public List generate(int left, int right, String current, List result) {
        if(left == 0 && right == 0) {
            result.add(current);
            return result;
        }
        
        if(left > 0) {
            generate(left - 1, right, current + '(', result);
        }
        
        if(right > left) {
            generate(left, right - 1, current + ')', result);
        }
        return result;
    }
}
Or: StringBuilder比String更加省空间,但是要注意返回的时候将最后一位删除
public class Solution {
    public List generateParenthesis(int n) {
        ArrayList res = new ArrayList();
        if (n <= 0) {
            return res;
        }
        
        int left = n;
        int right = n;
        StringBuilder sb = new StringBuilder();
        generate(left, right, sb, res);
        
        return res;
    }
    
    public void generate(int left, int right, StringBuilder sb, ArrayList res) {
        if (left == 0 && right == 0) {
            res.add(sb.toString());
            return;
        }
        
        if (left > 0) {
            generate(left - 1, right, sb.append("("), res);
            sb.deleteCharAt(sb.length() - 1);  //还原现场
        }
        
        if (right > left) {
            generate(left, right - 1, sb.append(")"), res);
            sb.deleteCharAt(sb.length() - 1);  //还原现场
        }
    }
}

Populating Next Right Pointers in Each Node II

Follow up for problem "Populating Next Right Pointers in Each Node".
What if the given tree could be any binary tree? Would your previous solution still work?
Note:
  • You may only use constant extra space.
For example,
Given the following binary tree,
         1
       /  \
      2    3
     / \    \
    4   5    7
After calling your function, the tree should look like:
         1 -> NULL
       /  \
      2 -> 3 -> NULL
     / \    \
    4-> 5 -> 7 -> NULL

Solution1: recursion
/**
 * Definition for binary tree with next pointer.
 * public class TreeLinkNode {
 *     int val;
 *     TreeLinkNode left, right, next;
 *     TreeLinkNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void connect(TreeLinkNode root) {
        if(root == null)
            return;
            
        if(root.left != null) {
            if(root.right != null)   //如果右子树不为空,左子树连右子树
                root.left.next = root.right;
            else {
                TreeLinkNode p = root.next;
                while(p != null && p.left == null && p.right == null) {   //如果左右子树为空,找下一个有子树的node
                    p = p.next;
                }
                if(p != null) {
                    if(p.left != null) {
                        root.left.next = p.left;
                    }else if(p.left == null && p.right != null) {
                        root.left.next = p.right;
                    }
                }
            }
        }
        
        if(root.right != null) {
            TreeLinkNode p = root.next;
            while(p != null && p.left == null && p.right == null) {
                    p = p.next;
            }
            if(p != null) {
                if(p.left != null) {
                    root.right.next = p.left;
                }else if(p.left == null && p.right != null) {
                    root.right.next = p.right;
                }
            }
        }

        connect(root.right);
        connect(root.left);
    }
}
Solution2: iteration. 横着连就是 level order traversal. 通常使用BFS,但是题目中每个node自带next指针,相当于就是自带Queue了。所以,从上向下一层一层的连接即可。 每层: a. 记录下一层的开始点 b. 连接各个Nodes.
时间复杂度和空间复杂度不变,还是O(n)和O(1).
/**
 * Definition for binary tree with next pointer.
 * public class TreeLinkNode {
 *     int val;
 *     TreeLinkNode left, right, next;
 *     TreeLinkNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void connect(TreeLinkNode root) {
        if(root == null)
            return;
        
        while(root != null) {
            TreeLinkNode levelStart = null;
            TreeLinkNode pre = null;
            for(; root != null; root = root.next) {
                if(levelStart == null) {
                    if(root.left != null)
                        levelStart = root.left;
                    else if(root.left == null && root.right != null)
                        levelStart = root.right;
                }
                
                if(root.left != null) {
                    if(pre != null) 
                        pre.next = root.left;
                    pre = root.left;
                }
                if(root.right != null) {
                    if(pre != null)
                        pre.next = root.right;
                    pre = root.right;
                }
            }
            root = levelStart;
        }
    }
}

Populating Next Right Pointers in Each Node

Given a binary tree
    struct TreeLinkNode {
      TreeLinkNode *left;
      TreeLinkNode *right;
      TreeLinkNode *next;
    }
Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL.
Initially, all next pointers are set to NULL.
Note:
  • You may only use constant extra space.
  • You may assume that it is a perfect binary tree (ie, all leaves are at the same level, and every parent has two children).
For example,
Given the following perfect binary tree,
         1
       /  \
      2    3
     / \  / \
    4  5  6  7
After calling your function, the tree should look like:
         1 -> NULL
       /  \
      2 -> 3 -> NULL
     / \  / \
    4->5->6->7 -> NULL
Solution1: recursion
Use stack space
/**
 * Definition for binary tree with next pointer.
 * public class TreeLinkNode {
 *     int val;
 *     TreeLinkNode left, right, next;
 *     TreeLinkNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void connect(TreeLinkNode root) {
        if(root == null)
            return;
            
        if(root.left != null) {   
            root.left.next = root.right;  //connect 2 and 3
        }
        
        if(root.right != null && root.next != null) {
            root.right.next = root.next.left;  //connect 5 and 6
        }
        
        connect(root.left);
        connect(root.right);
    }
}
Solution2: iteration
这里每一层我们会维护成一个链表,这个链表其实就是每层起始的那个队列,因此我们只需要维护一个链表的起始指针就可以依次得到整个队列了。接下来就是有左加左入链表,有右加右入链表,知道链表没有元素了说明到达最底层了。算法的复杂度仍然是对每个结点访问一次,所以是run time complexity is O(n),而空间上因为不需要额外空间来存储队列了,所以是space complexity is O(1)
/**
 * Definition for binary tree with next pointer.
 * public class TreeLinkNode {
 *     int val;
 *     TreeLinkNode left, right, next;
 *     TreeLinkNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void connect(TreeLinkNode root) {
        if(root == null)
            return;
        
        TreeLinkNode lastHead = root;
        TreeLinkNode curHead = null;
        TreeLinkNode pre = null;
        
        while(lastHead != null) {
            TreeLinkNode lastCur = lastHead;
            while(lastCur != null) {
                if(lastCur.left != null) {
                    if(curHead == null) {
                        curHead = lastCur.left;
                        pre = curHead;
                    }else {
                        pre.next = lastCur.left;
                        pre = pre.next;
                    }
                }
                
                if(lastCur.right != null) {
                    if(curHead == null) {
                        curHead = lastCur.right;
                        pre = curHead;
                    }else {
                        pre.next = lastCur.right;
                        pre = pre.next;
                    }
                }
                lastCur = lastCur.next;
            }
            lastHead = curHead;
            curHead = null;
        }
    }
}

Distinct Subsequences

Given a string S and a string T, count the number of distinct subsequences of T in S.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).
Here is an example:
S = "rabbbit"T = "rabbit"
Return 3.
Solution: DP
When you see string problem that is about subsequence or matching, dynamic programming method should come to your mind naturally. The key is to find the changing condition.
首先设置动态规划数组res[i][j],表示S串中从开始位置到第i位置与T串从开始位置到底j位置匹配的子序列的个数。
 如果S串为空,那么res[0][j]都是0;
 如果T串为空,那么res[i][j]都是1,因为空串为是任何字符串的字串。
这里我们维护res[i][j],对应的值是S的前i个字符和T的前j个字符有多少个可行的序列(注意这道题是序列,不是子串,也就是只要字符按照顺序出现即可,不需要连续出现)。
假设我们现在拥有之前的历史信息,我们怎么在常量操作时间内得到res[i][j]。假设S的第i个字符和T的第j个字符不相同,那么就意味着res[i][j]的值跟res[i-1][j]是一样的,前面该是多少还是多少,而第i个字符的加入也不会多出来任何可行结果。
如果S的第i个字符和T的第j个字符相同,那么所有res[i-1][j-1]中满足的结果都会成为新的满足的序列,当然res[i-1][j]的也仍是可行结果,所以res[i][j]=res[i-1][j-1]+res[i-1][j]
所以综合上面两种情况,递推式应该是res[i][j]=(S[i]==T[j]?res[i-1][j-1]:0)+res[i-1][j]
算法进行两层循环,时间复杂度是O(m*n),而空间上只需要维护当前i对应的数据就可以,也就是O(m)
public class Solution {
    public int numDistinct(String S, String T) {
        if(S.length() == 0)  //if s is empty, no distinct subsequence
            return 0;
        
        if(T.length() == 0)  //if T is empty, empty is a subsequence
            return 1;
        
        int[] res = new int[T.length() + 1];
        res[0] = 1;  //initial state
        
        for(int i = 0; i < S.length(); i++) {
            for(int j = T.length() - 1; j >= 0; j--) {
                if(S.charAt(i) == T.charAt(j)) {
                    res[j + 1] += res[j];
                }
            }
        }
        
        return res[T.length()];
    }
}

Longest Palindromic Substring


Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
Solution:
Define P[ i, j ] true if the substring Si ... Sj is a palindrome, otherwise false. Therefore,
P[ i, j ] ( P[ i+1, j-1 ] and Si = Sj )
The base cases are:
P[ i, i ]true
P[ i, i+1 ] ( Si = Si+1 )

O(n2) runtime, O(1) space – Simpler solution: 
A palindrome can be expanded from its center, and there are only 2n – 1 (n number as center, and n-1 center between two letters) such centers. The center of a palindrome can be in between two letters. Such palindromes have even number of letters (such as “abba”) and its center are between the two ‘b’s.
Since expanding a palindrome around its center could take O(n) time, the overall complexity is O((2*n-1)*n) = O(n2). Space complexity is O(1).
public class Solution {
    public String longestPalindrome(String s) {
        if(s == null || s.length() == 0)
            return null;
        
        int start = 0, end = 0;
        
        for(int i = 0; i < s.length(); i++) {
            int len1 = helper(s, i, i); //get longest palindrome with center of i
            int len2 = helper(s, i, i + 1); //get longest palindrome with center between i, i+1
            int len = Math.max(len1, len2);
            
            if(len > end - start) { //replace with the longest palindrom substring
                end = i + len / 2;
                start = i - (len - 1) / 2;
            }
        }
        return s.substring(start, end + 1);
    }
    
    public int helper(String s, int left, int right) {
        while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }
        
}

Wednesday, February 25, 2015

Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.
Solution: DP, buttom-up
这是一道动态规划的题目,求一个三角形二维数组从顶到低端的最小路径和。思路是维护到某一个元素的最小路径和,那么在某一个元素i,j的最小路径和就是它上层对应的相邻两个元素的最小路径和加上自己的值,递推式是sum[i][j]=min(sum[i-1][j-1],sum[i-1][j])+triangle[i][j]。最后扫描一遍最后一层的路径和,取出最小的即可。每个元素需要维护一次,总共有1+2+...+n=n*(n+1)/2个元素,所以时间复杂度是O(n^2)。而空间上每次只需维护一层即可(因为当前层只用到上一层的元素),所以空间复杂度是O(n).
换个角度考虑一下,如果这道题不自顶向下进行动态规划,而是放过来自底向上来规划,递归式只是变成下一层对应的相邻两个元素的最小路径和加上自己的值,res[j] = Math.min(res[j], res[j + 1]) + triangle.get(i).get(j). 原理和上面的方法是相同的,这样考虑到优势在于不需要最后对于所有路径找最小的,而且第一个元素和最后元素也不需要单独处理了,所以代码简洁了很多。
public class Solution {
    public int minimumTotal(List> triangle) {
        if(triangle == null || triangle.size() == 0)
            return 0;
        
        int[] res = new int[triangle.size()];
        
        for(int i = 0; i < triangle.size(); i++) {
            //get all numbers in last row
            res[i] = triangle.get(triangle.size() - 1).get(i);
        }
        for(int i = triangle.size() - 2; i >= 0; i--) {
            for(int j = 0; j <= i; j++) {
                res[j] = Math.min(res[j], res[j + 1]) + triangle.get(i).get(j);
            }
        }
        return res[0];
    }
}