Woodstock Blog

a tech blog for general algorithmic interview questions

[LintCode] Majority Number III

Question

link

Given an array of integers and a number k, the majority number is the number that occurs more than 1/k of the size of the array. Find it.

Note: There is only one majority number in the array

Example: For [3,1,2,3,2,3,3,4,4,4] and k = 3, return 3

Analysis

Similar to ‘Majority Number II’, but a little more difficult.

Instead of keeping 2 value for checking, now keep k values. Since values are constantly checked for existance, using a HashMap looks like a great idea.

Another idea suggest by G4G is a mechanism similar to the famous Tetris Game. The size of the buffer is k. The buffer is full, we remove all items by counter of 1. When counter reach 0, remove that item. In this way, the elements left in the buffer are the majority numbers.

This method is also used in counting highly-frequent string keywrods. For example, another post [Design] Big Data - Real Time Top K discusses about it.

Code

I did not write code for this question. Shouldn’t be difficult.

[LintCode] Majority Number II

Question

link

Given an array of integers, the majority number is the number that occurs more than 1/3 of the size of the array. Find it.

Note: There is only one majority number in the array

Example: For [1, 2, 1, 2, 1, 3, 3] return 1

Analysis

Similar to ‘Majority Number’, but a little more difficult.

Instead of keeping 1 value for checking, now keep 2 values. If the new number is same as any of the 2 values, decrease the count. If the new number is a totally new one, decrease both. The coding is a bit difficult and lengthy.

Note that 2 values are found in the end, do another iteration thru the array, and find the correct result from these 2 values.

Code

public int majorityNumber(ArrayList<Integer> nums) {
    // write your code
    if (nums == null || nums.size() < 3) {
        return -1;
    }
    int num1 = nums.get(0);
    int p = 1;
    int len = nums.size();
    while (p < len && nums.get(p) == num1) {
        p++;
    }
    int num2 = nums.get(p);
    int count1 = p;
    int count2 = 1;
    for (int i = p + 1; i < len; i++) {
        if (nums.get(i) == num1) {
            count1++;
        } else if (nums.get(i) == num2) {
            count2++;
        } else {
            // a totally different value
            if (count1 == 0) {
                num1 = nums.get(i);
                count1++;
            } else if (count2 == 0) {
                num2 = nums.get(i);
                count2++;
            } else {
                count1--;
                count2--;
            }
        }
    }
    if (count1 == 0 && count2 == 0) {
        return -1;
    } else if (count1 == 0) {
        return num2;
    } else if (count2 == 0) {
        return num1;
    } else {
        // it's gotta be either num1 or num2
        int count = 0;
        for (Integer n: nums) {
            if (n == num1) {
                count ++;
            }
        }
        if (count >= len / 3 + 1) {
            return num1;
        } else {
            return num2;
        }
    }
}

[LintCode] Majority Number

Question

link

How to find the majority elelment in an array in O(n)?

Note: The majority element is the element that occurs more than half of the size of the array

Example: For [1, 1, 1, 1, 2, 2, 2], return 1

Analysis

This question is called Moore’s Voting Algorithm (or Majority Vote Algorithm).

Psudo-code from GFG:

findCandidate(a[], size)
1.  Initialize index and count of majority element
     maj_index = 0, count = 1
2.  Loop for i = 1 to size – 1
    (a)If a[maj_index] == a[i]
        count++
    (b)Else
        count--;
    (c)If count == 0
        maj_index = i;
        count = 1
3.  Return a[maj_index]

Sometimess majority element does not exist, so you might want to do another iteration to double check.

If the check shows invalid, then there’s no majoirty element.

Code

public int majorityNumber(ArrayList<Integer> nums) {
    // write your code
    if (nums == null || nums.size() == 0) {
        return 0;
    }
    int num = nums.get(0);
    int count = 1;
    for (int i = 1; i < nums.size(); i++) {
        if (count == 0) {
            num = nums.get(i);
            count = 1;
        } else if (nums.get(i) == num) {
            count++;
        } else {
            count--;
        }
    }
    return num;
}

[Question] Topology Sort

Question

link

Topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.

A topological ordering is possible if and only if the graph has no directed cycles, that is, if it is a directed acyclic graph (DAG).

Analysis

Canonical application of toposort is to schedule tasks with dependency (project management etc.) It’s also used for computing formulas, logic synthesis, order of compilation (‘make’ command) and data serialization.

Solution

The usual algorithms have linear run-time, i.e. O(V + E).

First step is to find a list of “start nodes” which have no incoming edges. Then insert them into a set S and delete it. At least one such node must exist in an directed acyclic graph. From a university lecture.

From Wiki:

L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
    remove a node n from S
    add n to tail of L
    for each node m with an edge e from n to m do
        remove edge e from the graph
        if m has no other incoming edges then
            insert m into S
if graph has edges then
    return error (graph has at least one cycle)
else 
    return L (a topologically sorted order)

If the graph is a DAG, a solution will be contained in the list L (the solution is not necessarily unique). Otherwise, the graph must have at least one cycle and therefore a topological sorting is impossible.

Code

The solution comes from here.

public class Graph {
    private int vertices;
    private Set<Node> nodes = new HashSet<Node>();

    public Graph(int vertices) {
        this.vertices = vertices;
    }

    public void addVertex(Node node) {
        this.nodes.add(node);
    }

    public Set<Node> topologicalSort() {
        Queue<Node> q = new LinkedList<Node>();
        Set<Node> topoSort = new LinkedHashSet<Node>();
        int vertexProcessesCtr = 0;
        for (Node m : this.nodes) {
            vertexProcessesCtr = addToQueue(m, topoSort, vertexProcessesCtr, q);
        }
        while (!q.isEmpty()) {
            Node m = q.poll();
            for (Node child : m.getAdjacenctNode()) {
                int indeq = child.getInDegree() - 1;
                child.setInDegree(indeq);
                vertexProcessesCtr = addToQueue(child, topoSort,
                        vertexProcessesCtr, q);
            }
        }
        if (vertexProcessesCtr > this.vertices) {
            System.out.println();
        }
        return topoSort;
    }

    private int addToQueue(Node node, Set<Node> topoSort, int vertexProcess,
            Queue<Node> q) {
        if (node.getInDegree() == 0) {
            q.add(node);
            topoSort.add(node);
            return vertexProcess + 1;
        }
        return vertexProcess;
    }

    public static void main(String[] args) {
        Graph g = new Graph(8);

        Node TEN = new Node("10");
        Node ELEVEN = new Node("11");
        Node TWO = new Node("2");
        Node THREE = new Node("3");
        Node FIVE = new Node("5");
        Node SEVEN = new Node("7");
        Node EIGHT = new Node("8");
        Node NINE = new Node("9");

        SEVEN.AdjacenctNode.add(ELEVEN);
        ELEVEN.inDegree++;
        SEVEN.AdjacenctNode.add(EIGHT);
        EIGHT.inDegree++;
        FIVE.AdjacenctNode.add(ELEVEN);
        ELEVEN.inDegree++;
        THREE.AdjacenctNode.add(EIGHT);
        EIGHT.inDegree++;
        THREE.AdjacenctNode.add(TEN);
        TEN.inDegree++;
        ELEVEN.AdjacenctNode.add(TEN);
        TEN.inDegree++;
        ELEVEN.AdjacenctNode.add(TWO);
        TWO.inDegree++;
        ELEVEN.AdjacenctNode.add(NINE);
        NINE.inDegree++;
        EIGHT.AdjacenctNode.add(NINE);
        NINE.inDegree++;

        g.nodes.add(TWO);
        g.nodes.add(THREE);
        g.nodes.add(FIVE);
        g.nodes.add(SEVEN);
        g.nodes.add(EIGHT);
        g.nodes.add(NINE);

        System.out.println("Now calling the topologial sorts");
        Set<Node> result = g.topologicalSort();
        for (Node node : result) {
            System.out.println(node.data + " ");
        }
    }
}

class Node {
    public String data;
    public int dist;
    public int inDegree;
    LinkedList<Node> AdjacenctNode = new LinkedList<Node>();

    public void addAdjNode(final Node Child) {
        AdjacenctNode.add(Child);
        Child.inDegree++;
    }

    public Node(String data) {
        super();
        this.data = data;
    }

    public int getInDegree() {
        return inDegree;
    }

    public void setInDegree(int inDegree) {
        this.inDegree = inDegree;
    }

    public LinkedList<Node> getAdjacenctNode() {
        return AdjacenctNode;
    }
}

[LintCode] Previous Permutation

Question

link

Given a list of integers, which denote a permutation.

Find the previous permutation in ascending order.

Note

The list may contains duplicate integers.

Example

For [1,3,2,3], the previous permutation is [1,2,3,3]

For [1,2,3,4], the previous permutation is [4,3,2,1]

Analysis

This is almost the same question as “Next Permutation”.

  1. Find
  2. Swap
  3. Replace

Code

public ArrayList<Integer> previousPermuation(ArrayList<Integer> nums) {
    // write your code
    if (nums == null || nums.size() == 0) {
        return null;
    }
    int len = nums.size();
    int p = len - 2;
    // 1. find 1st increasing position from the back
    while (p >= 0 && nums.get(p) <= nums.get(p + 1)) {
        p--;
    }
    // 2. swap p with the first smaller value from the back
    if (p != -1) {
        int q = len - 1;
        while (nums.get(q) >= nums.get(p)) {
            q--;
        }
        swap(nums, p, q);
    }
    // swap array in range of (p+1, end)
    int left = p + 1;
    int right = len - 1;
    while (left < right) {
        swap(nums, left++, right--);
    }
    return nums;
}

private void swap(ArrayList<Integer> num, int p, int q) {
    int temp = num.get(p);
    num.set(p, num.get(q));
    num.set(q, temp);
}

[NineChap 6] Graph and Search

Graph

For graph, there are only 2 high-frequency questions, which is ‘clone graph’ and ‘topology sorting’.

Question list

  1. Clone Graph - difficult

  2. Topology Sorting

Search

Search have either DFS or BFS.

First, we will cover permutations and combinations using DFS. In this section we solve the famous N-queens question.

Then, there’s a few BFS questions. Graph traversal is BFS, and Word ladder is also a classic BFS question.

Question list

  1. Subsets

  2. Subsets II

    difficult

  3. Permutations

  4. Permutations II - difficult

  5. N-Queens

    how to use hashmap (and some space) to make it faster? 3 hashmaps to store the row, the (x,y) diff and sum. This will make isValid() method O(1).

  6. N-Queens II

  7. Next Permutation

  8. Previous Permutation

  9. Palindrome Partitioning

  10. Palindrome Partitioning II

  11. Combination Sum

  12. Combination Sum II

  13. Word Ladder

  14. Word Ladder II

Additional questions

  1. Restore IP Addresses

  2. Combinations

  3. Letter Combinations of a Phone Number

  4. Permutation Sequence

Code

Graph

Clone Graph

public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
    if (node == null) return null;
    HashMap<UndirectedGraphNode, UndirectedGraphNode> map = 
            new HashMap<UndirectedGraphNode, UndirectedGraphNode>();
    Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>();
    map.put(node, new UndirectedGraphNode(node.label));
    queue.add(node);
    while (!queue.isEmpty()) {
        UndirectedGraphNode cur = queue.remove();
        UndirectedGraphNode copy = map.get(cur);
        // here the 'copy' must exist. why? because all neighbors 
        // has been added to the map when they're pushed to queue.
        // so 'cur' must have a corresponding copy in the hashmap. 
        for (UndirectedGraphNode neib: cur.neighbors) {
            if (!map.containsKey(neib)) {
                queue.add(neib);
                map.put(neib, new UndirectedGraphNode(neib.label));
            }
            copy.neighbors.add(map.get(neib));
        }
    }
    return map.get(node);
}

Topology Sorting

A new post is written for it.

Search

Subsets

public List<List<Integer>> subsets(int[] num) {
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    if (num == null || num.length == 0) {
        return ans;
    }
    Arrays.sort(num);
    helper(ans, new LinkedList<Integer>(), num, 0);
    return ans;
}

private void helper(List<List<Integer>> ans, List<Integer> path, int[] num, int pos) {
    ans.add(new LinkedList<Integer>(path));
    for (int i = pos; i < num.length; i++) {
        path.add(num[i]);
        helper(ans, path, num, i + 1);
        path.remove(path.size() - 1);
    }
}

Subsets II

public List<List<Integer>> subsetsWithDup(int[] num) {
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    if (num == null || num.length == 0) {
        return ans;
    }
    Arrays.sort(num);
    helper(ans, new LinkedList<Integer>(), num, 0);
    return ans;
}

private void helper(List<List<Integer>> ans, List<Integer> path, int[] num, int pos) {
    ans.add(new LinkedList<Integer>(path));
    for (int i = pos; i < num.length; i++) {
        if (i > pos && num[i - 1] == num[i]) {
            continue;
        }
        path.add(num[i]);
        helper(ans, path, num, i + 1);
        path.remove(path.size() - 1);
    }
}

Permutations

public List<List<Integer>> permute(int[] num) {
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    if (num == null || num.length == 0) {
        return ans;
    }
    helper(ans, new LinkedList<Integer>(), num);
    return ans;
}

private void helper(List<List<Integer>> ans, List<Integer> path, int[] num){
    if (path.size() == num.length) {
        ans.add(new LinkedList<Integer>(path));
    }
    for (int i = 0; i < num.length; i++) {
        if (path.contains(num[i])) {
            continue;
        }
        path.add(num[i]);
        helper(ans, path, num);
        path.remove(path.size() - 1);
    }
}

Permutations II

public List<List<Integer>> permuteUnique(int[] num) {
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    if (num == null || num.length == 0) {
        return ans;
    }
    Arrays.sort(num);
    helper(ans, new LinkedList<Integer>(), num, new int[num.length]);
    return ans;
}

private void helper(List<List<Integer>> ans, List<Integer> path, int[] num, int[] visited){
    if (path.size() == num.length) {
        ans.add(new LinkedList<Integer>(path));
        return;
    }
    for (int i = 0; i < num.length; i++) {
        if (visited[i] == 1) {
            continue;
        }
        if (i > 0 && visited[i - 1] == 1 && visited[i] == 0 && num[i - 1] == num[i]) {
            // if current number is same as previous, then don't visit current
            continue;
        }
        path.add(num[i]);
        visited[i] = 1;

        helper(ans, path, num, visited);

        path.remove(path.size() - 1);
        visited[i] = 0;
    }
}

N-Queens

一次通关!高兴。

public List<String[]> solveNQueens(int n) {
    List<String[]> ans = new LinkedList<String[]>();
    if (n <= 0) {
        return ans;
    }
    helper(ans, new int[n], n, 0);
    return ans;
}

private void helper(List<String[]> ans, int[] path, int n, int pos) {
    if (pos >= n) {
        ans.add(convert(path, n));
        return;
    }
    for (int i = 0; i < n; i++) {
        path[pos] = i;
        if (!isValid(path, pos)) {
            continue;
        }
        helper(ans, path, n, pos + 1);
    }
}

private String[] convert(int[] path, int n) {
    String[] ans = new String[n];
    for (int j = 0; j < n; j++) {
        ans[j] = "";
        for (int i = 0; i < n; i++) {
            ans[j] += (j == path[i]) ? 'Q' : '.';
        }
    }
    return ans;
}

private boolean isValid(int[] path, int pos) {
    for (int i = 0; i < pos; i++) {
        // check path[i] and path[pos]
        if (path[i] == path[pos]) {
            return false;
        }
        if (path[i] - path[pos] == pos - i) {
            return false;
        }
        if (path[pos] - path[i] == pos - i) {
            return false;
        }
    }
    return true;
}

N-Queens II

int total;

public int totalNQueens(int n) {
    if (n <= 0) {
        return 0;
    }
    helper(new int[n], n, 0);
    return total;
}

private void helper(int[] path, int n, int pos) {
    if (pos >= n) {
        total++;
        return;
    }
    for (int i = 0; i < n; i++) {
        path[pos] = i;
        if (!isValid(path, pos)) {
            continue;
        }
        helper(path, n, pos + 1);
    }
}

private boolean isValid(int[] path, int pos) {
    for (int i = 0; i < pos; i++) {
        // check path[i] and path[pos]
        if (path[i] == path[pos]) {
            return false;
        }
        if (path[i] - path[pos] == pos - i) {
            return false;
        }
        if (path[pos] - path[i] == pos - i) {
            return false;
        }
    }
    return true;
}

Next Permutation

public void nextPermutation(int[] num) {
    if (num == null || num.length <= 1) {
        return;
    }
    int len = num.length;
    int p = len - 2;
    while (p >= 0 && num[p] >= num[p + 1]) {
        p--;
    }
    if (p < 0) {
        Arrays.sort(num);
    } else {
        int k = len - 1;
        while (k >= 0 && num[k] <= num[p]) {
            k--;
        }
        swap(num, p, k);
        reverse(num, p + 1, len - 1);
    }
}

private void swap(int[] num, int p, int k) {
    num[p] = num[p] + num[k];
    num[k] = num[p] - num[k];
    num[p] = num[p] - num[k];
}

private void reverse(int[] num, int s, int d) {
    while (s < d) {
        swap(num, s++, d--);
    }
}

Previous Permutation

Plz look at the new post.

Palindrome Partitioning

一次通关 again!very 高兴。

public ArrayList<ArrayList<String>> partition(String s) {
    ArrayList<ArrayList<String>> ans = new ArrayList<ArrayList<String>>();
    if (s == null || s.length() == 0) {
        return ans;
    }
    boolean[][] map = palinMap(s);
    helper(ans, new ArrayList<String>(), s, map, 0);
    return ans;
}

private void helper(ArrayList<ArrayList<String>> ans, ArrayList<String> path, String s, boolean[][] map, int pos) {
    int len = s.length();
    if (pos == len) {
        ans.add(new ArrayList<String>(path));
        return;
    }
    for (int i = pos; i < len; i++) {
        if (!map[pos][i]) {
            continue;
        }
        path.add(s.substring(pos, i + 1));
        helper(ans, path, s, map, i + 1);
        path.remove(path.size() - 1);
    }
}

private boolean[][] palinMap(String s) {
    int len = s.length();
    boolean[][] map = new boolean[len][len];
    for (int i = len - 1; i >= 0; i--) {
        for (int j = 0; j < len; j++) {
            if (i > j) {
                continue;
            } else if (i == j) {
                map[i][j] = true;
            } else if (j - i == 1) {
                map[i][j] = s.charAt(i) == s.charAt(j);
            } else {
                map[i][j] = map[i + 1][j - 1] & 
                        s.charAt(i) == s.charAt(j);
            }
        }
    }
    return map;
}

Palindrome Partitioning II

This is DP, not Graph & Search.

Combination Sum

public ArrayList<ArrayList<Integer>> combinationSum(int[] candidates, int target) {
    ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
    if (candidates == null || candidates.length == 0) {
        return ans;
    }
    Arrays.sort(candidates);
    helper(ans, new ArrayList<Integer>(), candidates, target, 0);
    return ans;
}

private void helper(ArrayList<ArrayList<Integer>> ans, ArrayList<Integer> path, 
        int[] candidates, int target, int pos) {
    if (target == 0) {
        ans.add(new ArrayList<Integer>(path));
        return;
    } else if (target < 0) {
        return;
    }
    for (int i = pos; i < candidates.length; i++) {
        if (target < candidates[i]) {
            continue;
        }
        path.add(candidates[i]);
        helper(ans, path, candidates, target - candidates[i], i);
        path.remove(path.size() - 1);
    }
}

Combination Sum II

public ArrayList<ArrayList<Integer>> combinationSum2(int[] candidates, int target) {
    ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
    if (candidates == null || candidates.length == 0) {
        return ans;
    }
    Arrays.sort(candidates);
    helper(ans, new ArrayList<Integer>(), candidates, target, 0);
    return ans;
}

private void helper(ArrayList<ArrayList<Integer>> ans, ArrayList<Integer> path, 
        int[] candidates, int target, int pos) {
    if (target == 0) {
        ans.add(new ArrayList<Integer>(path));
        return;
    } else if (target < 0) {
        return;
    }
    for (int i = pos; i < candidates.length; i++) {
        if (target < candidates[i]) {
            continue;
        }
        if (i > pos && candidates[i - 1] == candidates[i]) {
            continue;
        }
        path.add(candidates[i]);
        helper(ans, path, candidates, target - candidates[i], i + 1);
        path.remove(path.size() - 1);
    }
}

Word Ladder

Note that this is a BFS question, not DFS. I made it wrong and it took me a long time.

public int ladderLength(String start, String end, Set<String> dict) {
    Queue<String> queue = new LinkedList<String>();
    queue.add(start);
    int length = 1;

    while (!queue.isEmpty()) {
        int currentSize = queue.size();
        for (int k = 0; k < currentSize; k++) {
            String word = queue.remove();
            // insert all adjacent strings of word
            if (word.equals(end)) {
                return length;
            }
            for (int i = 0; i < word.length(); i++) {
                char[] letters = word.toCharArray();
                char originalLetter = letters[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == originalLetter) continue;
                    letters[i] = c;
                    String newLetters = String.valueOf(letters);
                    if (dict.contains(newLetters)) {
                        queue.add(newLetters);
                        dict.remove(newLetters);
                    }
                }
                letters[i] = originalLetter;
            }
        }
        length++;
    }
    return 0;
}

Word Ladder II

unsolvable

Additional questions

Restore IP Addresses

public List<String> restoreIpAddresses(String s) {
    List<String> ans = new ArrayList<String>();
    if (s == null || s.length() == 0) {
        return ans;
    }
    helper(ans, new ArrayList<String>(), s, 0);
    return ans;
}

private void helper(List<String> ans, List<String> path, String s, int pos) {
    if (path.size() == 4) {
        if (pos == s.length()) {
            ans.add(convert(path));
        }
        return;
    }
    for (int i = pos + 1; i <= s.length() && i <= pos + 3; i++) {
        String nextNum = s.substring(pos, i);
        if (!isValid(nextNum)) {
            continue;
        }
        path.add(nextNum);
        helper(ans, path, s, i);
        path.remove(path.size() - 1);
    }
}

private boolean isValid(String str) {
    if (str.length() == 1) {
        return true;
    } else if (str.charAt(0) == '0') {
        return false;
    } else {
        int num = Integer.parseInt(str);
        return 0 <= num && num <= 255;
    }
}

private String convert(List<String> path) {
    String str = "";
    for (String s: path) {
        str += "." + s;
    }
    return str.substring(1);
}

Combinations

skip

Letter Combinations of a Phone Number

skip

Permutation Sequence

public String getPermutation(int n, int k) {
    List<Integer> list = new ArrayList<Integer>();
    int fact = 1;
    for (int i = 1; i <= n; i++) {
        list.add(i);
        fact *= i;
    }
    String ans = "";
    for (int i = n; i > 0; i--) {
        fact = fact / i;
        int rank = (k - 1) / fact;
        k = (k - 1) % fact + 1;

        int curNum = list.remove(rank);
        ans += String.valueOf(curNum);
    }
    return ans;
}

Conclusion

DFS (O(2n), O(n!))

  1. Find all possible solutions
  2. Permutations / Subsets

BFS (O(m), O(n))

  1. Graph traversal
  2. Find shortest path in a simple graph

Two most canonical BFS questions:

  1. Graph traversal and toposort
  2. Word Ladder

[Brain Teaser] Khan Academy 8 Brain Teasers

Question List

link

link2

  1. Liar truth-teller (skip)
  2. Toggler brain teaser
  3. Alien abduction
  4. Blue forehead room
  5. Forehead numbers
  6. Light bulb switching
  7. Path counting (skip)
  8. 3D path counting

Toggler brain teaser

“你前面站了5个人,他们中间只有一个人讲真话……”

这个问题比上个问题难就难在,你只知道他们五个中有一个只讲真话,但其余四个,他们有时候讲真话,有时候讲假话,只有一点可以确定,这四个人将真话和假话有个规律:如果这次讲了真话,下次就会讲假话,如果这次讲假话,下次就讲真话。你的任务是,把五个人中那个只讲真话的人找出来。

你可以问两个问题,两个问题可以向同一个人发问,也可以分别问两个人。

你该问什么问题?

小提示:你可以这样安排两个问题承担的任务:首先你可以先问一个问题,不管得到的答案是什么,你都能从中知道下一个问题你将得到的答案是真是假。

求职者的最佳答案:

随便找一个人,首先问:“你是那个只讲真话的吗?”如果答案是肯定的,你再问这个人:“谁是只讲真话的?”;如果第一个问题你得到的答案是否定的,你就再问对方“谁不是只讲真话的?”

正如这个问题给出的提示,第一个问题的价值在于,如果你得到的答案是“我是”,那么你问的人要么是那个只讲真话的,要么是那个这一轮讲假话的“半真话半假话”者,不管是谁,他下一轮一定会说真话。所以你可以继续问这个人:“谁是只讲真话的?”对方的答案就是正确答案。

如果对第一个问题你得到的答案是“我不是”,那么回答者不可能是只讲真话的那个人,只能是一个此轮讲真话的“半真话半假话”者。此人下一轮将会说假话,所以你应该问他:“谁不是只讲真话的?”同样他告诉你的,只能是那个只讲真话的。

Alien abduction

“外星人打算将地球用来种蘑菇,并且已经抓了十个人类……”

外星人用这十个人代表地球60亿人口,将通过外星人的方式来测试这十个人,决定地球是不是有资格加入跨星际委员会,如果没有,就把地球变成一个蘑菇农场。

明天,这十个人将被关在一间漆黑的屋子里前后排成一队,外星人将给每个人戴一顶帽子,帽子为紫色或者绿色,然后外星人会将灯打开,这十个人每个人都无法看见自己头上的帽子是什么颜色,但可以看见排在你前面的每个人头上帽子的颜色。

帽子的颜色是随机的,可能全是紫的,也可能全是绿的,或者是任意的组合。

外星人会从后往前问每一个人:“你头上的帽子是什么颜色?”如果这个人答对了,这个人就安然无事,他所代表的地球上6亿人口也将获救。否则,这个人将被爆头,外星人将把他所代表的6亿人口变成蘑菇的肥料。每个人的答案屋子里所有人都可以听到。

现在,人类的命运在你手上,你可以设计一个方案,使这十个人提前制定一个计划,这个计划必须拯救尽可能多的人。

提示:有个方案可以让你拯救其中至少九个人。

求职者的最佳答案:

第十个人计算排在前面的所有人的绿帽子是奇数还是偶数并向前面的人发出一个信号,这样排在前面人就可以再通过排在更前面的所有人的绿帽子的奇偶数是否变化来判断自己帽子的颜色,因为如果绿帽子奇偶发生变化,那自己就是那个导致变化的“绿帽子”,如果没变化,自己就是“紫帽子”。

因为所有的人除了回答外星人的问题不能说话,所以第十个人的“信号”只能包含在自己的答案里,比如如果排在前面的九个人有奇数顶绿帽子,这个人类就告诉外星人自己的帽子是“绿色”,如果是偶数,就猜自己的帽子是“紫色”。这样等于给他前面的人一个暗号,排在他前面的这个人,可以通过计算自己前面的所有人的绿帽子的奇偶变化来判断自己的帽子是绿还是紫。

排在最后的那个人为了大众利益没有选择,根据前面的人的帽子情况告诉外星人自己是“绿帽子”还是“紫帽子”,他的答案有1/2的几率正确,但他前面的人一定都能答对。

还没懂?比如第十个人看到前面有奇数个绿帽子,他就告诉外星人自己的是绿色,这是他前面的人就知道他的意思是前面九个人中有奇数个绿帽子,这是第九个人再数前面八个人的,如果前面八个人中也有奇数个,那自己就是紫色帽子。第九个人告诉外星人自己是紫色帽子,第八个人就知道绿帽子没有减少还是奇数个,再数数前面七个人绿帽子数的奇偶,就可以判断自己帽子的颜色;反之,如果第九个人告诉外星人自己是绿色帽子,那第八个人就应该知道绿色帽子减少了一个由奇数变成了偶数,再看看前面所有的绿帽子情况作出判断。这样一个接一个,只要每个人都认真听后面的人的答案并在心里计算所剩绿帽子的奇偶变化,前面九个人都能获救。

当然,你也可以计算紫色帽子的奇偶。

Blue forehead room

“100个完美的逻辑学家坐在一个房间里……”

这是一个电视真人秀节目,节目里100个拥有完美无瑕逻辑思维能力的人围成一圈坐在一个房间里。在进入房间前,这100个人被告知,100个人中至少有一个人的额头是蓝色的。你可以看见别人额头的颜色,但无法看到自己的,你需要对自己额头是不是蓝色进行猜测,在房间的灯被关掉时,如果你推测出你的额头是蓝色的,你需要站起来离开房间。

然后房间的灯被再次打开,那些认为自己额头是蓝色的人已经不在屋内。接下来灯会再次被关掉,剩下的人中推测自己额头是蓝色的离开房间,如此重复。

问题来了,假设这100个人的额头都是蓝色的,将会发生什么情况?注意,这100个人都有完美无瑕的逻辑推理能力,他们会根据其他人的额头颜色对自己进行合理的推理和猜测。

提示:想想看,如果100个人不全是蓝色额头,又会发生什么情况?

求职者的最佳答案:

将会出现的情况是:灯关了又开,开了又关,重复到第一百次时,所有人都同时离开。

这是为什么呢?想想看,每个人都看见其他99个人额头是蓝色的,灯关掉后再打开,发现这99个蓝色额头的同伴都没有离开,然后灯再次关掉后打开,如此重复100遍后,所有人同时离开了房间。

这么理解吧,假设只有一个人的额头是蓝色的,由于这100个人事先被告知至少有一个人额头是蓝色,所以这个人如果看到其他99个人额头都不是蓝色,立马就知道自己是蓝色,所以灯一关掉,这个人就会离开房间。

如果有两个人额头是蓝色呢?

其中一个蓝色额头的人会想:我的额头可能是蓝色也可能不是蓝色,现在其他99个人中有一个蓝色额头的人,如果我不是蓝色,那么就只有这一个人是,那么他看到我们都不是蓝色额头就能推断出他是,那么灯一关他就会离开,我先等一下,灯再打开如果他已经走了,那就证明我的额头不是蓝色的。

反之,如果我的额头是蓝色的,那个蓝色额头的人的想法会和我刚才的想法一样先等一等,第一次关灯他不会离开,这样如果灯开了那个蓝色额头的人还在,就证明我的额头也是蓝色的。这样第二次关灯我们俩会一起离开。

以此类推,如果有三个人额头是蓝色,你看到另外两个人额头是蓝色,应该推算出如果自己的额头不是蓝色的话,那么灯第二次关的时候他们俩会同时离开,如果他们俩没有同时离开,那就证明我的额头是蓝色的,我应该在第三次关灯的时候离开。结果是,三个蓝色额头的人在第三次关灯的时候同时离开。

把上述逻辑重复一百遍,你就得到了最上面的正确答案。

Forehead numbers

“逻辑学家们围成一圈坐着,他们的额头上面画有数字……”

又来一个逻辑学家围成一圈的问题,这次是这样的,三个拥有完美逻辑推理能力的人围成一圈坐在一个房间里,每个人的额头上都画着一个大于0的数字,三个人的数字各不相同,每个人都看得见其他两个人的数字,看不见自己的。

这三个数字的情况是,其中一个数字是其他两个数字的和,已知的情况还有,其中一个逻辑学家的数字是20,一个是30。

游戏组织者从这三个逻辑学家后面走过,并问三个人各自额头上的数字是什么。但第一轮每个逻辑学家都回答他们无法推测自己的数字是什么。游戏组织者只好进行第二轮的发问,这是为什么?你能据此猜出三个逻辑学家的数字吗?

求职者的最佳答案:

结果由第三个逻辑学家的答案而定。他们三个的数字分别是20,30和50。

假设第二个和第三个逻辑学家额头上的数字是20和30,这时候如果第一个逻辑学家的数字是10,那么第二个逻辑学家看到其他两个人一个是10,一个是30,会想:“我要么是20,要么是40。”

第三个逻辑学家看到其他两个人一个是10,一个是20,会想:“我要么是30,要么是10,但我不会是10,因为每个数字都不一样,所以我应该是30。”

这样第三个逻辑学家就会猜出自己的数字是30了,但他没有,第一轮谁也没有准确推测出自己的数字,这说明我们的前提不正确,第一个逻辑学家的数字不是10,那么他只能是50。

Light bulb switching

“你面前有一百个灯泡,排成一排……”

一百个灯泡排成一排,第一轮你把他们全都打开亮着,然后第二轮,你每隔一个灯泡关掉一个,这样所有排在偶数的灯泡都被关掉了。

然后第三轮,你每隔两个灯泡,将开着的灯泡关掉,关掉的灯泡打开(也就是说将所有排在3的倍数的灯泡的开关状态改变)。

以此类推,你将所有排在4的倍数的灯泡的开关状态改变,然后将排在5的倍数的灯泡开关状态改变……

第100轮的时候,还有几盏灯泡亮着?

提示:如果你是第n轮(n大于1小于100),排在n的倍数位置的灯泡的开关状态就发生转变。

反过来,比如第8个灯泡,当你在8的因子轮(即第1,2,4和8轮)的时候,它就会改变开关状态。所以对于第m个灯泡,如果m有奇数个因子,你的开关状态就发生奇数次变化。

求职者的最佳答案:

10盏灯泡亮着,这10盏灯泡排位数都是平方数。

根据提示已经可以看出,这个问题的实质就是找出有多少个灯泡的排位数拥有奇数个因子。每拥有一个因子,到这个因子数的那一轮时,这个灯泡就会被转换开关状态。

比如第1轮,因为所有100个数字都有因数1,所以全部被打开;第2轮,只有那些拥有2这个因子、能被2整除的数字的灯泡转换状态被关掉;第3轮,只有那些拥有3这个因子、能被3整除的数字的灯泡被转换状态。以此类推,如果灯泡排位数拥有奇数个因子,意味着它被打开和关上奇数次,那它就最终还是被打开的状态,如果灯泡排位数拥有偶数个因子,那它最终就是被关上的状态。

比如第1个灯泡有奇数个因子,第2个有偶数个(1,2),第3个有偶数个(1,3)第4个有奇数个(1,2,4),所以 第4个灯泡最后还是亮着的。

最终计算得出,所有排位数为平方数的灯泡最终还是亮着的,因为这些数都拥有奇数个因子,1,4,9,16……

在100以内,共有10个平方数,分别是1,4,9,16,25,36,49,64,81,100。这10个排位数的灯泡,最终都还是亮着。

3D path counting (most difficult)

“你有一个立方体,立方体的边长是3……”

这个问题比前面那个从左上格子走到右下格子的问题难,因为那毕竟是个平面问题。如图所示,这次的任务是从立方体的背面左上的小立方体走到完全相对的正面右下小立方体。

你可以往上移,也可以往下移,还可以往前移。You can movetoward the front, you can move down, or you can move upward。

问题还是,你共有几种走法?

求职者的最佳答案:

90种,思路是将这个立方体分成“三层”。

上面平面图的那道题的思路就是个最好的提示。你可以将这个立方体分成“三层”,粉红色代表最上面那层,紫色代表中间那层,橘红色代表下面那层。

现在,我们把问题变成了:从左边、右边和上边到达目标小立方体的走法共有多少(如图所示,即到达紫色中间层最右下脚方块以及橘红色最右下脚左边以及上边相邻方块的方法)?假设从起点小立方体到达终点小立方体左边相邻小立方体共有m种方法,到达右边相邻小立方体共有n种方法,到达上边相邻小立方体有r 种方法,那我们需要求出来的,就是n+m+r。

按照前面那道平面题的思路和方法,你就可以一点一点计算出来我们的正确答案。

1 1 1
1 2 3
1 3 6

Layer 1

1 2 3
2 6 12
3 12 30

Layer 2

1 3 6
3 12 30
6 30 90

Layer 3

A better and more elegant mathamatics solution is available here

[NineChap 5.1] Dynamic Programming

Dynamic Programming

The fundamental of DP is ‘merorized search’. It’s easy to implement but bad for memory. And it’s generally useless in the industry.

When to use DP?

  1. Input cannot sort
  2. Find minimum/maximum result
  3. Check the feasibility
  4. Count all possible solutions

If question asks you to find all possible solutions, it’s gonna be DFS, not DP.

5 Types of DP

  1. Matrix DP (10%)
  2. Sequence/Two Sequences DP (80%)
  3. Interval DP (5%)
  4. Tree DP (5%)
  5. States Compressing DP (0%)

Type 3 to 5 are less important.

Question List

Type 1: Matrix DP

  1. Triangle

  2. Unique Paths

  3. Unique Paths II

  4. Minimum Path Sum

Type 2.1: Sequence Dp

  1. Climbing Stairs

  2. Jump Game

  3. Jump Game II - tricky, index handling

  4. Palindrome Partitioning II

  5. Word Break

  6. Word Break II

  7. Decode Ways - tricky, initial state

  8. Longest Increasing Subsequence

Type 2.2: Two Sequences Dp

  1. Distinct Subsequences - difficult, state transition formula

  2. Edit Distance

  3. Interleaving String

  4. Longest Common Subsequence

Type 3: Interval Dp

Merge Stone

Type 4: Tree Dp

  1. Binary Tree Maximum Path Sum

Type 5: States Compressing DP

Ignore.

Additional questions

  1. Maximum Subarray

  2. Coin Change Problem

Code

Type 1: Matrix DP

Triangle

public int minimumTotal(List<List<Integer>> triangle) {
    if (triangle == null || triangle.size() == 0) {
        return 0;
    }
    int len = triangle.size();
    int[][] dp = new int[len][len];
    for (int i = len - 1; i >= 0; i--) {
        // bottom-up approach (row by row)
        for (int j = 0; j <= i; j++) {
            if (i == len - 1) {
                dp[i][j] = triangle.get(i).get(j);
            } else {
                dp[i][j] = triangle.get(i).get(j)
                        + Math.min(dp[i+1][j], dp[i+1][j+1]);
            }
        }
    }
    return dp[0][0];
}

Unique Paths

public int uniquePaths(int m, int n) {
    if (m == 0 && n == 0) {
        return 0;
    }
    int[][] dp = new int[m][n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (i == 0 || j == 0) {
                dp[i][j] = 1;
            } else {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
    }
    return dp[m-1][n-1];
}

Unique Paths II

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    if (obstacleGrid == null) {
        return 0;
    }
    int m = obstacleGrid.length;
    int n = obstacleGrid[0].length;
    int[][] dp = new int[m][n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (obstacleGrid[i][j] != 0) {
                dp[i][j] = 0;
            } else if (i == 0 && j == 0) {
                dp[i][j] = 1;
            } else if (i == 0) {
                dp[i][j] = dp[i][j-1];
            } else if (j == 0) {
                dp[i][j] = dp[i-1][j];
            } else {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
    }
    return dp[m-1][n-1];
}

Minimum Path Sum

public int minPathSum(int[][] grid) {
    if (grid == null) {
        return 0;
    }
    int m = grid.length;
    int n = grid[0].length;
    int[][] dp = new int[m][n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (i == 0 && j == 0) {
                dp[i][j] = grid[i][j];
            } else if (i == 0) {
                dp[i][j] = dp[i][j-1] + grid[i][j];
            } else if (j == 0) {
                dp[i][j] = dp[i-1][j] + grid[i][j];
            } else {
                dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
            }
        }
    }
    return dp[m-1][n-1];
}

Type 2.1: Sequence Dp

Climbing Stairs

public int climbStairs(int n) {
    if (n <= 2) {
        return n;
    }
    int[] dp = new int[n];
    dp[0] = 1;
    dp[1] = 2;
    for (int i = 2; i < n; i++) {
        dp[i] = dp[i - 2] + dp[i - 1];
    }
    return dp[n - 1];
}

Jump Game

public boolean canJump(int[] A) {
    if (A == null || A.length == 0) 
        return false;
    int reach = 0;
    int len = A.length;
    for (int i = 0; i < len; i++) {
        if (i > reach) 
            break;
        reach = Math.max(reach, i + A[i]);
        if (reach >= len - 1)   
            return true;
    }
    return false;
}

Jump Game II

public int jump(int[] A) {
    if (A == null || A.length <= 1) 
        return 0;
    int[] dp = new int[A.length];
    int cur = 1;
    for (int i = 0; i < A.length - 1; i++) {
        while (cur <= i + A[i] && cur < dp.length) {
            dp[cur] = dp[i] + 1;
            cur++;
        }
        if (cur == dp.length) {
            break;
        }
    }
    return dp[A.length - 1];
}

Palindrome Partitioning II

public int minCut(String s) {
    if (s == null || s.length() <= 1) {
        return 0;
    }
    boolean[][] map = palindromeMap(s);
    int len = s.length();
    int[] dp = new int[len + 1];
    dp[0] = -1;
    for (int i = 1; i <= len; i++) {
        dp[i] = Integer.MAX_VALUE;
        for (int j = 1; j <= i; j++) {
            if (map[i-1][j-1]) {
                dp[i] = Math.min(dp[i], dp[j-1] + 1);
            }
        }
    }
    return dp[len];
}

private boolean[][] palindromeMap(String s) {
    int len = s.length();
    boolean[][] map = new boolean[len][len];
    char[] ss = s.toCharArray();
    for (int i = 0; i < len; i++) {
        for (int j = i; j >= 0; j--) {
            if (i == j) {
                map[i][j] = true;
            } else if (i - j == 1) {
                map[i][j] = ss[i] == ss[j];
            } else {
                map[i][j] = (ss[i] == ss[j]) & map[i - 1][j + 1];
            }
        }
    }
    return map;
}

Word Break

public boolean wordBreak(String s, Set<String> dict) {
    if (s == null || s.length() == 0) {
        return true;
    }
    int len = s.length();
    boolean[] dp = new boolean[len + 1];
    dp[0] = true;
    for (int i = 1; i <= len; i++) {
        for (int j = 0; j < i; j++) {
            if (!dp[j]) {
                continue;
            }
            if (dict.contains(s.substring(j, i))) {
                dp[i] = true;
                break;
            }
        }
    }
    return dp[len];
}

Word Break II

My code is definitely correct, although it got TLE.

See original post for more.

Decode Ways

public int numDecodings(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    int len = s.length();
    int[] dp = new int[len + 1];
    dp[0] = 1;
    dp[1] = 1; // pay attention to the initial state
    if (!isValidNumber(s.substring(0, 1))) {
        return 0;
    }
    for (int i = 2; i <= len; i++) {
        if (isValidNumber(s.substring(i - 1, i))) {
            dp[i] += dp[i - 1];
        }
        if (isValidNumber(s.substring(i - 2, i))) {
            dp[i] += dp[i - 2];
        }
    }
    return dp[len];
}

private boolean isValidNumber(String input) {
    if (input.length() == 0 || input.length() > 2 || input.charAt(0) == '0') {
        return false;
    }
    int num = Integer.parseInt(input);
    return (1 <= num && num <= 26);
}

Longest Increasing Subsequence

There’s a new post in ‘Lintcode’ category.

Type 2.2: Two Sequences Dp

Distinct Subsequences

public int numDistinct(String S, String T) {
    int m = S.length(), n = T.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            if (j == 0) {
                dp[i][j] = 1;
            } else if (i == 0) {
                dp[i][j] = 0;
            } else {
                dp[i][j] = dp[i - 1][j];
                if (S.charAt(i - 1) == T.charAt(j - 1)) {
                    dp[i][j] += dp[i - 1][j - 1];
                }
            }
        }
    }
    return dp[m][n];
}

Edit Distance

public int minDistance(String A, String B) {
    if (A == null || B == null)
        return 0;
    int m = A.length(), n = B.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            if (i == 0) {
                dp[i][j] = j;
            } else if (j == 0) {
                dp[i][j] = i;
            } else {
                if (A.charAt(i - 1) == B.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]);
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - 1]);
                    dp[i][j]++;
                }
            }
        }
    }
    return dp[m][n];
}

Interleaving String

public boolean isInterleave(String s1, String s2, String s3) {
    if (s1 == null || s2 == null) {
        return false;
    }
    int m = s1.length(), n = s2.length();
    if (m + n != s3.length()) {
        return false;
    }
    boolean[][] dp = new boolean[m + 1][n + 1];
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            if (i == 0) {
                dp[i][j] = s2.substring(0, j).equals(s3.substring(0, j));
                continue;
            } else if (j == 0) {
                dp[i][j] = s1.substring(0, i).equals(s3.substring(0, i));
                continue;
            }
            if (i > 0 && dp[i - 1][j]
                    && s1.charAt(i - 1) == s3.charAt(i + j - 1)) {
                dp[i][j] = true;
            }
            if (j > 0 && dp[i][j - 1]
                    && s2.charAt(j - 1) == s3.charAt(i + j - 1)) {
                dp[i][j] = true;
            }
        }
    }
    return dp[m][n];
}

Longest Common Subsequence

There’s a new post in ‘Lintcode’ category.

Type 3: Interval Dp

Merge Stone

有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。

link

Solution explained:

sum[i[用于记录从第1堆到第i堆(包含i)石子的总重量。

dp[i][j]表示从第i堆(包含i)到第j堆(包含j)石子的合并的最小代价。

状态转移方程为:dp[i][j] = minimize{dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]}, k从i到j(不包含j)。

len=2表示第一次合并的情况,此时合并的石子为2堆。此时,i从1到n-len+1,j=i+len-1。

Quoted from this blog and the solution is well explained on wikiio.

Type 4: Tree Dp

Binary Tree Maximum Path Sum

This is a difficult question, but I solved it. I feel happy.

private class ResultType {
    int maxPath;
    int depth;

    ResultType(int a, int b) {
        this.maxPath = a;
        this.depth = b;
    }
}

public int maxPathSum(TreeNode root) {
    if (root == null) {
        return 0;
    }
    return helper(root).maxPath;
}

private ResultType helper(TreeNode node) {
    if (node == null) {
        return new ResultType(Integer.MIN_VALUE, 0);
    }
    ResultType ll = helper(node.left);
    ResultType rr = helper(node.right);
    int maxPath = node.val + ll.depth + rr.depth;
    maxPath = Math.max(maxPath, ll.maxPath);
    maxPath = Math.max(maxPath, rr.maxPath);
    int depth = 0;
    depth = Math.max(depth, node.val + Math.max(ll.depth, rr.depth));
    return new ResultType(maxPath, depth);
}

Additional questions

Maximum Subarray

public int maxSubArray(int[] A) {
    if (A == null || A.length == 0) {
        return 0;
    }
    int len = A.length;
    int[] dp = new int[len];
    dp[0] = A[0];
    for (int i = 1; i < len; i++) {
        dp[i] = A[i];
        if (dp[i - 1] > 0)
            dp[i] += dp[i - 1];
    }
    int max = Integer.MIN_VALUE;
    for (Integer i : dp)
        max = Math.max(max, i);
    return max;
}

[LintCode] Longest Increasing Subsequence

Question

link

Given a sequence of integers, find the longest increasing subsequence (LIS).

You code should return the length of the LIS.

Example

For [5, 4, 1, 2, 3], the LIS  is [1, 2, 3], return 3

For [4, 2, 4, 5, 3, 7], the LIS is [4, 4, 5, 7], return 4

Analysis

This is one of the 2 most popular questions of DP. This is a sequences Dp. The equation isn’t difficult. Time complexity of DP solution is O(n2).

There’s also a binary search solution which the time complexity is O(nlgn). It’s very complex, and very hard to explain, but I’ll try:

Maintain an array called ‘array’. A[i] denotes the tail of sequence for LIS = i. Initially A[0] = first element of the input, then keep inserting elements into this array. It’s explained in this post.

I will give an example for the input: 0, 8, 4, 12, 2

Our strategy determined by the following conditions:

  1. If A[i] is smallest among all end candidates of active lists, we will start new active list of length 1.

  2. If A[i] is largest among all end candidates of active lists, we will clone the largest active list, and extend it by A[i].

  3. If A[i] is in between, we will find a list with largest end element that is smaller than A[i]. Clone and extend this list by A[i]. We will discard all other lists of same length as that of this modified list.

A[0] = 0. Case 1. There are no active lists, create one.

array = 0

A[1] = 8. Case 2. Clone and extend.

array = 0, 8

A[2] = 4. Case 3. Clone, extend and discard.

array = 0, 4

A[3] = 12. Case 2. Clone and extend.

array = 0, 4, 12

A[4] = 2. Case 3. Clone, extend and discard.

array = 0, 2, 12

So the LIS is (0, 2, 12) of length 3.

Code

DP solution, by me

public int longestIncreasingSubsequence(int[] nums) {
    // write your code here
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int len = nums.length;
    int[] dp = new int[len];
    dp[0] = 1;
    for (int i = 1; i < len; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (nums[j] <= nums[i]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    int lis = 1;
    for (Integer seq : dp) {
        lis = Math.max(lis, seq);
    }
    return lis;
}

Binary search solution, C++ code from this post. I don’t think I am able code this solution.

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;

#define ARRAY_SIZE(A) sizeof(A)/sizeof(A[0])
// Binary search (note boundaries in the caller)
// A[] is ceilIndex in the caller
int CeilIndex(int A[], int l, int r, int key) {
    int m;

    while( r - l > 1 ) {
        m = l + (r - l)/2;
        (A[m] >= key ? r : l) = m; // ternary expression returns an l-value
    }

    return r;
}

int LongestIncreasingSubsequenceLength(int A[], int size) {
    // Add boundary case, when array size is one

    int *tailTable   = new int[size];
    int len; // always points empty slot

    memset(tailTable, 0, sizeof(tailTable[0])*size);

    tailTable[0] = A[0];
    len = 1;
    for( int i = 1; i < size; i++ ) {
        if( A[i] < tailTable[0] )
            // new smallest value
            tailTable[0] = A[i];
        else if( A[i] > tailTable[len-1] )
            // A[i] wants to extend largest subsequence
            tailTable[len++] = A[i];
        else
            // A[i] wants to be current end candidate of an existing subsequence
            // It will replace ceil value in tailTable
            tailTable[CeilIndex(tailTable, -1, len-1, A[i])] = A[i];
    }

    delete[] tailTable;

    return len;
}

int main() {
    int A[] = { 2, 5, 3, 7, 11, 8, 10, 13, 6 };
    int n = ARRAY_SIZE(A);

    printf("Length of Longest Increasing Subsequence is %d\n",
            LongestIncreasingSubsequenceLength(A, n));

    return 0;
}

[LintCode] Longest Common Subsequence

Question

link

Given two strings, find the longest comment subsequence (LCS).

Your code should return the length of LCS.

Example

For "ABCD" and "EDCA", the LCS is "A" (or D or C), return 1

For "ABCD" and "EACB", the LCS is "AC", return 2

Analysis

This is one of the 2 most popular questions of DP.

This is a two-sequences Dp. The equation is not difficult to build. (consider only the last element of the DP array when building the state transition equation)

Code

public int longestCommonSubsequence(String A, String B) {
    // write your code here
    if (A == null || B == null) {
        return 0;
    }
    int m = A.length(), n = B.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (A.charAt(i - 1) == B.charAt(j - 1)) {
                dp[i][j] = 1 + dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}