Monday, April 30, 2012

Find the Longest Increasing Sub-sequence -- LeetCode 300

[Question]
Given an array of integers A of length n, find the longest sequence i1, i2, ... ik such that ij < ij+1 and A[ ij ] < A[ ij+1 ] for any j, 0<j<k.

For example, {1,3,5,2,7} --> {1,3,5,7} and {0,5,1,2,6,4} --> {0,1,2,4}.

[Analysis]
This is a classic Dynamic Programming (DP) case. A solution is given in book "Algorithm For Interviews". 
First, we define a function of Si, which refers the longest sequence that ends at A[i]. Then, Si is equal to the longest Sj +1, when j<i and A[j]<A[i].
Using a table, the algorithm can populate S[i] from 0 to the end of array A (S0 = 1). 

Note: 
1) To find the longest increasing consecutive sub-sequence would be simpler. DP is not needed.
2) There are a few other questions derived from this: e.g. find longest common sub-strings between two strings; or find the longest palindromic sub-sequence.

[Solution]

List<Integer> findLongestIncreasingSequence(int[] a) {
int[] s = new int[a.length];
int[] prev= new int[a.length];  //used to retrieve the list.

s[0] = 1;
for (int i=0; i<prev.length; i++) prev[i] =-1;

int maxLength =0;
int maxEndIndex =0;

for (int i=1; i<a.length; i++) {
for (int j=0; j<i; j++) {
if (a[i]>a[j] && s[i]<s[j]+1) {
s[i] = s[j]+1;
prev[i] = j;
}
}

if (s[i]>maxLength) {
maxLength = s[i];
maxEndIndex = i;
}
}

int i=maxEndIndex;
List<Integer> list = new ArrayList<Integer>();
while (prev[i] != -1) {
list.add(a[i]);
i = prev[i];

list.add(a[i]);
return list;
}

//
// -- O(NLogN) ---
//
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> res;
        for (auto& n: nums) {
            auto it = lower_bound(res.begin(), res.end(), n);
            if (it==res.end()) res.push_back(n);
            else *it = n;
        }
        return res.size();
    }

};

Saturday, April 28, 2012

Minimum Window Sub-string (a.k.a Sliding Window) -- LeetCode 76

[Question]

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
For example,
S = "ADOBECODEBANC"
T = "ABC"
Minimum window is "BANC".
[Analysis]
The problem can be resolved by using a window (tail, head) on string S.
1) Both indexes tail and head start from index 0 of S.
2) Move "head" forward step by step in S until find a window (tail, head) contain all the characters in T.
3) Then move "tail" forward to leave characters (in T) out of window, as many as possible until the sub-string in window cannot contain all characters in T.
4) Repeat 2, 3 until reach the end of S. Meanwhile, measure the length of window and record the minimum window as requested.

The time complexity is O(N).
[Solution]
//
//--- C++ ---
//
class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> target;
        for (auto c: t) 
            target[c]++;
            
        unordered_map<char, int> counter;
        auto contained=[&]() {
            for (auto& t: target) 
                if (t.second>counter[t.first]) return false;
            return true;
        };
        
        int i=0, j=0;
        int ri=0, rj=INT_MAX;
        while (j<s.size()) {
            while (!contained() && j<s.size()) 
                counter[s[j++]]++;
            
            while(contained() && i<j) {
                if (j-i<rj-ri)
                    ri=i, rj=j;
                counter[s[i++]]--;   
            }
        }
        return rj-ri<=s.size()?s.substr(ri, rj-ri):"";
    }
};
//
//--- Java ---
//
public class MinimumWindowString implements TestCaseRunner {

    public String minWindow(String S, String T) {
        if (S.length() < T.length() || T.length()==0) return "";
        String minStr = S+" ";
        S.toUpperCase();
        T.toUpperCase();

       // -- cnt arrays used for judging whether all characters 
       //  in target string are included in window sub-string.
        int[] wndCnts = new int[26];
        int[] tgtCnts = new int[26];
     
        char[] temp = T.toCharArray();
        for (char c: temp) {
            tgtCnts[c-'A'] ++;
        }
     
        int loadFlag = setFlag(T);
        int loadFlagCopy = loadFlag;
     
        int head, tail;
        head = tail =0;
     
        // -- skip letters that not meaningful --
        while (!charInTarget(S.charAt(head), tgtCnts)) {
        head++;
        };      
        tail = head;
     
        // -- start looping --
        while (head < S.length() ) {
            while (!charInTarget(S.charAt(tail), tgtCnts)) {
            tail++;
            };
         
            // -- grow to the match --
            while (head < S.length() && loadFlag!=0) {
                char c= S.charAt(head++);
                if (charInTarget(c, tgtCnts)) {
                    wndCnts[c-'A']++;
                    if (wndCnts[c-'A']==tgtCnts[c-'A']) {
                        loadFlag ^= 1<<(c-'A');
                    }
                }              
            }

            if (loadFlag == 0) {
                if (minStr.length()>head-tail) {
                    minStr = S.substring(tail, head);
                }
            }
            else break;
         
            // -- reduce the match --
            int unloadFlag = 0;
            while(tail<head && unloadFlag==0) {
                char c= S.charAt(tail++);
                if (charInTarget(c, tgtCnts)) {
                    wndCnts[c-'A']--;
                    if (wndCnts[c-'A']<tgtCnts[c-'A']) {
                        unloadFlag = 1<<(c-'A');
                    }
                    else if (minStr.length()>head-tail) {
                        minStr = S.substring(tail, head);
                    }
                }
            }
         
            loadFlag = loadFlagCopy & unloadFlag;
        }
     
        if (minStr.equals(S+" ")) return "";
        return minStr;
    }
 
    int setFlag (String s) {
        char[] chars = s.toCharArray();
        int a = 0;
        for (char c: chars) {
            a |= 1<<(c-'A');
        }
        return a;
    }
 
    boolean charInTarget (char c, int[] target) {
        return (target[c-'A']>0);
    }
 
    public void runTestCases() {
String[] S = {"ADOBECODEBANC", "ABBADBABDRGBACCCB", "HBBHHCADBEBCAECBCCBB"};
String[] T = {"ABC", "ABBC", "ABBC"};

for (int i=0; i<S.length; i++) {
String ret = minWindow(S[i], T[i]);
System.out.println(ret);
}
    }
}


Friday, April 27, 2012

Construct a BST from In-order and Pre-order Traversal Result

[Question]
Construct a BST from in-order and pre-order traversal result.

For example,
Given pre-order traversal [6, 2, 4, 3, 5, 8, 7] and in-order traversal [2, 3, 4, 5, 6, 7, 8], the BST should be
[Analysis]
Pre-order traversal gives out the root of BST as the first element. Based on root, left and right sub-trees can be found in in-order traversal.
As in the example above, because 6 is the first element in pre-order traversal, 6 must be the root element. Then [2, 3, 4, 5] is the left sub-tree, and [7, 8] is the right sub-tree, according to in-order traversal.
             pre-order:  [6, [2, 4, 3, 5], [8, 7]]     in-order: [[2, 3, 4, 5], 6, [7, 8]]
The problem is reduced to (1) re-construct left sub-tree based on pre-order [2, 4, 3, 5] and in-order [2, 3, 4, 5], and (2) re-construct right sub-tree based on pre-order [8, 7] and in-order [7,8].

[Solution]
/*
 * public class BSTNode {
 *       int value;
 *       BSTNode left, right;
 * }
 */

    public static BSTNode constructBST (List<Integer> preOrder, List<Integer> inOrder) {
        assert preOrder.size() == inOrder.size();
        if (preOrder.size() == 0) return null;

        BSTNode node = new BSTNode();
        node.value = preOrder.get(0);

        int index = inOrder.indexOf(preOrder.get(0));
        node.left = constructBST(preOrder.subList(1, index+1), inOrder.subList(0, index));
        node.right= constructBST(preOrder.subList(index+1, preOrder.size()), inOrder.subList(index+1, inOrder.size()));
        return node;
    }  



Thursday, April 26, 2012

Regular Expression Matching


[Question]

Implement regular expression matching with support for '.' and '*'.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true

[Analysis]
This problem can be solved in a similar way with Edit Distance. According to its rules, there are four kinds of patterns in this regular expression matching:

"." - Matches any single character.
"c*" - Matches zero or more of character 'c';
".*" - Matches zero or more of any characters;
"c" - Matches a single character 'c';

First, make a match matrix between the word and patterns: e.g. pattern is ".*bc*." and the word is "abbcc"

     .*   b   c*   .
a    0   1   1    0
b    0   0   1    0
b    0   0   1    0
c    0   1   0    0
c    0   1   0    0

"0" in (i,j) represents a possible match between the pattern p[j] and character w[i].
"1" in (i,j) represents no match at all between the pattern p[j] and character w[i].

If there is a match between the pattern and word, there is a path from [1,1] to [m, n] (m is the length of the word and n is the number of patterns), which composed of a series of "0" and the following actions:
1) [i,j] -> [i+1,j+1]  -- end the pattern match at [i,j] and move to next pattern match to [i+1, j+1];
2) [i,j] -> [i,j+1]  -- skip the current pattern match at [i,j] and use next pattern (j+1) on character (i);
3) [i,j]->  [i+1, j] -- continue use current pattern at letter (i) and try to apply to (i+1);

2) 3) are operations allowed only for pattern columns "c*" and ".*".
1) is the only operation allowed for pattern columns "c" and ".".

So back to the above example, we found a path (a match) from [1,1] to [5,4] (marked by red color).


     .*   b   c*   .
   0   1   1    0
   0   0   1    0
   0   0   1    0
c    0   1   0    0
c    0   1   0    0 



[Solution]
//-------------
//--- C++ ---
//-------------
class Solution {
public:
    bool isMatch(const char *s, const char *p) {
        string s1 (s);
        string p1 (p);
     
        if ( p1.empty()) return s1 == "";
     
        vector<vector<bool>> map (s1.size()+1, vector<bool>(p1.size()+1, false));
     
        map[0][0] = true;
        for (int i=1; i<p1.size()+1; i++) {
            if (p1[i-1] == '*') map[0][i] = map[0][i-2];
        }
     
        for (int i=1; i<s1.size()+1; i++) {
            for (int j=1; j<p1.size()+1; j++) {
                if (p1[j-1] == '.') {
                    map[i][j] = map[i-1][j-1];
                }
                else if (p1[j-1] == '*') {
                    map[i][j] = map[i][j-2]     //'*' is counted as zero
                            || map[i][j-1]      //'*' is counted as 1
                            || p1[j-2] == '.' && map[i-1][j]  //'.*'
                            || s1[i-1] == p1[j-2] && map[i-1][j];
                }
                else
                    map[i][j] = (s1[i-1] == p1[j-1])? map[i-1][j-1] : false;
            }
        }
        return map.back().back();
    }
};

//---------------
//--- JAVA ---
//---------------
import java.util.*;

public class Solution {
    public static boolean isMatch(String s, String p) {

        List<String> pat = new ArrayList<String> ();
        char[] ap = p.toCharArray();
     
        if (s.length() == 0) return (p.length()==0 || p.equals(".*"));
     
        if (ap.length<=1) {
            if (ap.length==0 && s.length() ==1) return true;
            if (ap.length==0 && ap[0]=='.' && s.length() == 1) return true;
            return false;
        }
     
        // -- parse and generate patterns sub-strings.
        int i = 0;
        while(i<ap.length-1) {
            char a = ap[i];
            char b = ap[i+1];
            if (a == '.') {
                if (b=='*') {
                    pat.add(new String("") + a+b);
                    i++;
                } else {
                    pat.add(new String("") +a);
                }
            }
            else if (a != '*') {
                if (b=='*') {
                    pat.add(new String("") + a +b);
                    i++;
                } else {
                    pat.add(new String("") +a);
                }
            } else { // a=='*'
                return false;
            }
            i++;
        }
        if (i<ap.length) {
        pat.add(new String("") + ap[i]);
        }
     
        // -- generate the matrix.
        int[][] matrix;
        int rows = s.length();
        int cols = pat.size();      
        matrix = new int[rows][cols];

        for (i=0; i<rows; i++) {
            for (int j=0; j<cols; j++) {
                char cs = s.charAt(i);
                matrix[i][j] = 1;
                if (pat.get(j).length() == 2){
                    if ( pat.get(j).equals(".*") ){
                        matrix[i][j] = 0;
                    }
                    else { //"c*"
                        if (pat.get(j).charAt(0) == cs) {
                            matrix[i][j] = 0;
                        }
                    }
                }
                else {
                    if (pat.get(j).equals(".") || pat.get(j).charAt(0) == cs) {
                        matrix[i][j] = 0;
                    }
                }
                System.out.println(matrix[i][j]);
            }
            System.out.println("");
        }
     
       // -- to find a path/match.
        if (matrix[rows-1][cols-1] != 0) return false;
     
        return validateMatch(matrix, 0, 0, pat);
    }
 
    public static boolean validateMatch(int[][] m, int i, int j, List<String> pat) {
        int rows = m.length;
        int cols = m[0].length;
     
        if (i== rows-1 && j==cols-1) return true;

        boolean ret = false;
        if (pat.get(j).length()==2) {
            if (j+1<cols && m[i][j+1] == 0) {
                ret = ret || validateMatch(m, i, j+1, pat);
            }
            if (i+1<rows && m[i+1][j] ==0) {
                ret = ret || validateMatch(m, i+1, j, pat);
            }
        }
     
        if (j+1<cols && i+1<rows && m[i+1][j+1] ==0) {
                ret = ret || validateMatch(m, i+1, j+1, pat);
        }
     
        return ret;  
    }
 
public static void main(String args[])
{
String s = "aasdfasdfasdfasdfas";
String p =  "aasdf.*asdf.*asdf.*asdf.*s";

boolean a = Solution.isMatch(s, p);
System.out.println("a:" + a);
}
}


Generate Permutations

[Question]
Given a collection of numbers, return all possible permutations.
For example,
[1,2,3] have the following permutations:
[1,2,3][1,3,2][2,1,3][2,3,1][3,1,2], and [3,2,1].

[Analysis]
Do it in a recursive way.

[Solution]

public static List<List<Integer>> generatePermutations ( List<Integer> nums ) {
List<List<Integer>> result = new ArrayList<List<Integer>> ();

if (nums.isEmpty()) return null;

if (nums.size()==1) {
result.add(nums);
return result;
}

Integer a = nums.get(0);
int numSize = nums.size();
nums.remove(0);
List<List<Integer>> subPerms = generatePermutations(nums);

for (int i=0; i<subPerms.size(); i++) {
for (int j=0; j<numSize-1; j++) {
List<Integer> newPerm = new ArrayList<Integer> (subPerms.get(i));
newPerm.add(j, a);
result.add(newPerm);
}
List<Integer> newPerm = new ArrayList<Integer> (subPerms.get(i));
newPerm.add(a);
result.add(newPerm);
}
return result;
}

Minimum Triplet from 3 Arrays

[Question]
You are given with three sorted arrays ( in ascending order), you are required to find a triplet (one element from each array) such that distance is minimum.

Distance is defined like this :
If a[i], b[j] and c[k] are three elements then
      distance=max(abs(a[i]-b[j]),abs(a[i]-c[k]),abs(b[j]-c[k]))

Please give a solution in O(n) time complexity.



[Analysis]
Approach 1:
Assume the triplet with minimum distance is (a[i], b[j], c[k[) and b[j] is the median of the three numbers. This implies a[i] is the closest to b[j] among array a, and c[k] is the closest to b[j] among c[k]. 


So for a given j, if b[j] closest a[i] and closest c[j] are a[i]<=b[j]<=c[k] or c[k]<=b[j]<=a[i], then this triplet is one of the candidates for the minimum distance. 


The algorithm can scan array a, b, c and examine those candidates for the final result. The scan is in linear time. But this requires multiple scans which can be redundant.


Approach 2:
Suppose data in 3 arrays line up, the minimum triplet can must have the pattern of (X, Y, Y*, Z) where X, Y, Z come from different arrays (* denotes 0 or n appearances). So we can collect all such triplets that meet such pattern, measure their distances (which is Z-X) and find the minimum.


In code, a priority queue (that mimics heap) of size 3 is used. It ensures we get data from 3 arrays in a non-descending order. Then we found the pattern triplets (X, Y, Y*, Z) using class Pattern. Finally, the distances of all such triplets are calculated and measured. 

[Solution]

import java.util.*;


public class MinimumTripletTest {
public class Triplet {
int i, j, k;
}


public class Node implements Comparable{
char  arrId;   // 0-a, 1-b, 2-c
int  index;   // array index;
int  value;

Node (char t, int i, int v) {
arrId = t;
index = i;
value = v;
}


@Override
public int compareTo(Object a) {
// TODO Auto-generated method stub
if (!(a instanceof Node)) {
return -1;
}
Node n = (Node)a;

if (value< n.value) return -1;
if (value> n.value) return 1;
return 0;
}
}

public class Pattern {
List<Node> nodes;

Pattern() {
nodes = new ArrayList<Node> ();
}

boolean fulfilled() {
return nodes.size() == 3;
}

void add (Node a) {
if (nodes.size()==2 && a.arrId == nodes.get(1).arrId) {
nodes.set(1, a);
}
else if (nodes.size()==2 && a.arrId == nodes.get(0).arrId) {
nodes.set(0, nodes.get(1));
nodes.set(1, a);
}
else if (nodes.size()==1 && a.arrId == nodes.get(0).arrId) {
nodes.set(0,  a);
}
else {
nodes.add(a);
}
}

Node pop () {
if (nodes.size()==0) return null;
return nodes.remove(0);
}

int distance() {
if ( !fulfilled() ) return -1;
return nodes.get(2).value - nodes.get(0).value;
}

Triplet getTriplet() {
if (!fulfilled()) return null;
Triplet t = new Triplet();
for(Node n: nodes) {
if (n.arrId == 'a') {
t.i = n.index;
}
else if (n.arrId == 'b') {
t.j = n.index;
}
else if (n.arrId == 'c') {
t.k = n.index;
}
}
return t;
}
}

public Triplet findMinimumTriplet(int[] a, int[] b, int[] c) {
Pattern pat = new Pattern();
PriorityQueue<Node> pq = new PriorityQueue<Node>();

int min = Integer.MAX_VALUE;
int i,j,k;
i=j=k=0;

pq.add(new Node('a', i, a[i]));
pq.add(new Node('b', j, b[j]));
pq.add(new Node('c', k, c[k]));
Triplet t = null;

while (i<a.length || j<b.length || k<c.length) {
Node next = pq.remove();
pat.add(next);
if (pat.fulfilled()) {
if (pat.distance() < min) {
min = pat.distance();
t = pat.getTriplet();
}
pat.pop();
}

if (next.arrId == 'a' && ++i<a.length) {
pq.add(new Node('a', i, a[i]));

else if (next.arrId == 'b'&& ++j<b.length) {
pq.add(new Node('b', j, b[j]));
}
else if (next.arrId == 'c' && ++k<c.length) {
pq.add(new Node('c', k, c[k]));
}
}

return t;
}

public static void main (String[] args) {
int[] a = {1,3,5,7, 9, 10, 13, 15, 24, 28, 29, 30,};
int[] b = {12, 12, 14, 17, 19, 22, 25, 28};
int[] c = {4, 9, 15, 22, 28, 38, 49, 50};

MinimumTripletTest test = new MinimumTripletTest();
MinimumTripletTest.Triplet t = test.findMinimumTriplet(a, b, c);

System.out.println("result: "+a[t.i]+","+b[t.j]+","+c[t.k]);
}
}

// result: 28,28,28

Tuesday, April 24, 2012

The Minimum Value in a Queue

[Question]
How to design a queue A, in addition to insert(), delete(), also has a function min() which returns the minimum element? Can you do all functions in O(1) time?

[Analysis]
Need an additional queue X to record each potential minimum elements along the way. Those are elements in queue A that are smaller than the last incoming element (the end of the queue). When adding a new element,  queue X will need to remove the elements that are larger than the new element.

The time complexity for min(), insert() and delete() is amortized O(1).

[Solution]

import java.util.*;

public class QueueWithWeights implements TestCaseRunner{

// -- interface of weights
public interface WeightedObject {
public int getWeight();
}

// -- class WeightedString is for testing purpose;
public class WeightedString implements WeightedObject {
String str;
int wgt;

WeightedString (String a, int w) {
str = a;
wgt = w;
}
public int getWeight() {
return wgt;
}
}

// -- members of the queue;
List<WeightedObject> oList;
List<Integer> wList;

// -- constructor;
QueueWithWeights() {
oList = new ArrayList<WeightedObject> ();
wList = new ArrayList<Integer>();
}

public  void insert(WeightedObject a) {
oList.add(a);
int i = wList.size()-1;
while (i>=0 && a.getWeight()<wList.get(i)) {
wList.remove(i--); //amortized O(1);
}
wList.add(a.getWeight());
}

public  WeightedObject delete() {
if (oList.size()>0) {
if (oList.get(0).getWeight() == wList.get(0)) {
wList.remove(0);
}
return oList.remove(0);
}
return null;
}

public  int min() {
if (oList.isEmpty()) return Integer.MIN_VALUE;
return wList.get(0);
}

public void runTestCases() {
int[] w = {3, 4, 5, 8, 5, 3, 2, 1, 2, 3, 9 , 10, 9, 8, 7, 6, 5, 4, 3, 6, 7};
QueueWithWeights que = new QueueWithWeights();
int j = 3;

for (int i: w) {
WeightedString s = new WeightedString(String.valueOf(i), i);
que.insert(s);
System.out.println("inserted " + i);
System.out.println("min " + que.min());
j--;
if (j==0) {
System.out.println("delete "+que.delete().getWeight());
System.out.println("min " + que.min());
System.out.println("delete "+que.delete().getWeight());
System.out.println("min " + que.min());
j=3;
}
}
}
}

Clone a Directed Acyclic Graph (DAG)

[Question]
Write a function to copy a Directed Acyclic Graph (DAG) -- http://en.wikipedia.org/wiki/Directed_acyclic_graph.


class Node {
  int value;
  vector<Node *> neighbors;
};

Node *copyGraph(Node *root) {
   // add code here
}


[Analysis]
Using DFS (Depth First Search) and a recursive function. Note the copy of next node could be created before the DFS reaches it, so a hashmap is needed to record the nodes that have been created. 


In actual DAG, there may be many vertices which have no incoming edges. So there could be many starting vertices for DFS. This can be justified by using an extra node which reaches all starting vertices in the DAG. Using this extra node as root, the clone function will work the same.


The time complexity is O(n+m), n is number of vertices, m is number of edges.

[Solution]
// In Java

public class DAGraph {
public class Node {
int value;
List<Node> list;
}

public Node cloneDAGraph (Node root) {
return cloneDAGraph( root, new HashMap<Node,Node>() );
}

public Node cloneDAGraph (Node root, Map<Node, Node> map) {
if (root == null) return null;

if (map.containsKey(root)) return map.get(root);

Node newNode = new Node();
newNode.value = root.value;
newNode.list = new ArrayList<Node>();
map.put(root, newNode);

for(Node n: root.list) {
newNode.list.add(cloneDAGraph(n, map));
}
return newNode;
}
}