Woodstock Blog

a tech blog for general algorithmic interview questions

[LeetCode 131] Palindrome Partitioning

Question

link

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab",
Return

  [
    ["aa","b"],
    ["a","a","b"]
  ]

Stats

Frequency 4
Difficulty 3
Adjusted Difficulty 4
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is an extremely classic question.

It’s not very hard to solve, although it looked impossible at first.

Solution

The solution is DFS.

It’s very standard DFS code, I will not go into coding details. This type of “return all possible path” question is always related to DFS, keep this in mind!

The code is borrowing ideas from peking2’s blog. He also had an analysis on this series of questions:

我觉得一般有三种变形,解法各有不同。

  1. 最后结果是一个整数,比如Palindrome Partitioning II。这个用DP来解
  2. 最后求一个结果,比如最小切法。这个用DP+backtrack来解
  3. 求所有的结果。这个一般用DFS来解。

This question is the 3rd case. Fish Lei also said the same.

这种需要输出所有结果的基本上都是DFS的解法

Code

public ArrayList<ArrayList<String>> partition(String s) {
    // int len = s.length();
    ArrayList<ArrayList<String>> ans = new ArrayList<ArrayList<String>>();
    helper(ans, new ArrayList<String>(), s, 0);
    return ans;
}

private void helper(ArrayList<ArrayList<String>> ans, ArrayList<String> cur, 
        String s, int start) {
    int len = s.length();
    if (start == len) {
        ans.add(new ArrayList<String>(cur));
        return;
    }
    for (int i = start + 1; i <= len; i ++) {
        String sub = s.substring(start, i);
        if (isPal(sub)) {
            cur.add(sub);
            helper(ans, cur, s, i);
            cur.remove(cur.size() - 1);
        }
    }
}

private boolean isPal(String str) {
    int left = 0, right = str.length() - 1;
    while (left < right) {
        if (str.charAt(left) != str.charAt(right)) 
            return false;
        left++;
        right--;
    }
    return true;
}

[LeetCode 128] Longest Consecutive Sequence

Question

link

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

Stats

Frequency 3
Difficulty 4
Adjusted Difficulty 4
Time to use just coding is easy

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

I did not solve this question. We are going to make use of HashSet.

Information on HashSet from official document:

java.util.HashSet

This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element.

This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the buckets. Iterating over this set requires time proportional to the sum of the HashSet instance’s size (the number of elements) plus the “capacity” of the backing HashMap instance (the number of buckets). Thus, it’s very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.

Note that this implementation is not synchronized. If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be synchronized externally.

To summarize it, HashSet:

  1. implements Set interface

  2. implemented by using a hash table

  3. un-ordered

  4. add, remove and contains methods have constant time O(1)

  5. can have null element

  6. not sync

Solution

Well explained in this site.

Dump everything to a hash set.

Now go through the hashset. For each element, look up the set for all values neighboring the current value. Keep track of the largest sequence you can find, while removing the elements found from the set. Save the count for comparison.

Repeat this until the hashset is empty.

Assuming lookup, insertion and deletion are O(1) time, this algorithm would be O(N) time.

Updated on July 4th, 2014: Look at the 2nd for-loop. Here if I do ‘for (Integer in: set)’ to iterate all numbers, I will get “java.util.ConcurrentModificationException ”. This is because we are iterating while modifying. The most tricky part of this solution is iteration thru the array, instead of the set. Take note of that!

Code

public int longestConsecutive(int[] num) {
    int longest = 1;
    HashSet<Integer> set = new HashSet<Integer>();
    for (Integer in: num) set.add(in);
    for (Integer in: num) {
        int left = in - 1, right = in + 1;
        while (set.contains(left)) {
            set.remove(left);
            left --;
        }
        while (set.contains(right)) {
            set.remove(right);
            right ++;
        }
        longest = Math.max(longest, right - left - 1);
    }
    return longest;
}

[Design] DFS, BFS and Space Efficiency

First Word

This post talks about BFS, DFS and space efficiency.

Analysis

For BFS it’s easy to understand. It is implemented with a queue. Space usage is O(width)

For DFS, there are 2 types: true DFS and pseudo-DFS. True DFS space usage is O(depth), while psudo-DFS is O(depth x branching factor).

branching factor is the number of children at each node, the outdegree.

Pseudo-DFS is implemented by simply take the BFS implementation and replace the Queue with Stack. But true-DFS is a very different algorithm where the stack is use for backtracking only. This is a great article talking about this topic.

the true classic DFS cannot be obtained from BFS by such queue-to-stack replacement. The classic DFS is a completely different algorithm with significantly different inner structure. True DFS is a genuinely recursive algorithm that uses stack for backtracking purposes, not for storing the vertex discovery “front” (as is the case in BFS)

The following quoted texts are also worth-reading.

Something worth noting - Pseudo-DFS should give you O(depth * branching factor) space, as opposed to O(depth) for proper DFS, which is still better than O(width).

The defining characteristic that separates true DFS and what you call pseudo DFS is how they use the stack. True DFS use it to store backtracking information in contrast to pseudo DFS which used the stack to store the vertex discovery front.

Examples

Given a perfect binary search tree of 4 levels. BFS would require 8 space, while DFS requires only 4 space. The branching factor is 2, so true and pseudo DFS use same amount of space.

Given a star graph: a single central vertex surrounded by 1000 peripheral vertices, with each connected to the central vertex. If we run BFS on this graph, the queue size will immediately jump to 1000. The same thing happens for pseudo-DFS. But classic DFS algorithm will need stack depth of only 1 (!) to traverse this entire graph.

[LeetCode 125] Valid Palindrome

Question

link

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.

For example,
"A man, a plan, a canal: Panama" is a palindrome.
"race a car" is not a palindrome.

Note:
Have you consider that the string might be empty? This is a good question to ask during an interview.

For the purpose of this problem, we define empty string as valid palindrome.

Stats

Frequency 5
Difficulty 2
Adjusted Difficulty 1
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

Wow, it’s frequency 5!

This is a very classic and easy question.

Code

public boolean isPalindrome(String s) {
    int len = s.length();
    s = s.toLowerCase();
    int left = 0, right = len - 1;
    while (left < right) {
        while (left < len && !isAlphanumeric(s.charAt(left)))
            left ++;
        while (right >= 0 && ! isAlphanumeric(s.charAt(right)))
            right --;
        if (left == len && right == -1) return true;
        if (left == len || right == -1) return false;
        if (s.charAt(left) != s.charAt(right)) 
            return false;
        left ++;
        right --;
    }
    return true;
}

private boolean isAlphanumeric(char c) {
    return ('a' <= c && c <= 'z') 
        || ('0' <= c && c <= '9');
}

[LeetCode 114] Flatten Binary Tree to Linked List

Question

link

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.

Stats

Frequency 3
Difficulty 3
Adjusted Difficulty 4
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is a very difficult question, and there are many solutions.

Solution

Analysis of all solutions (except first one) is found in this great article.

Solution 1 is my code, I am make use of a ‘pre’ pointer in this recursive method. This idea is actually quite good, but is never seen in any other people’s blogs.

Solution 2 is the most standard recursive solution. It passes in a node, flatten it and return the last node after it’s flattened. So we can flatten root node’s left and right node respectively, and then connect it.

To flatten a binary tree, according to the given example, is to recursively insert the left subtree to the right subtree and append the original right subtree to the end of the left subtree, i.e.
     root                        root
     /  \            ->            \
  left  right                      left
                                     \
                                    right
Since we need to append the original right-tree to the end of the left subtree, we let the recursion function return the last node after flatten.

I missed “root.left = null” while coding, and it resulted in long long time debugging. I think this is a very silly mistake.

Solution 3 is making use of a stack. I have to say, although I can figure how it works, I am still blur about the thinking process of this entire idea. Which is to say, I can’t keep it in mind even after learning it.

I shall try write this code in the future.

We can also solve the problem without recursion.

To do that, we can use a stack to store all right subtrees when we prune them temporarily, and append each of them back after we go through the corresponding left subtree.

Basically, given a non-empty tree,
  • if it has left subtree, store the right subtree (if not null) to stack, move the left subtree to right;
  • if not, append back a subtree from stack to the current node's right;
  • continue to the right node until finish.

Solution 4 is what I believe the best solution. It simple uses while loop to put all nodes in the correct position, without recursion or stack.

Each time when we prune a right subtree, we use while-loop to find the right-most leaf of the current left subtree, and append the subtree there.

Analysis of all solutions (except first one) is found in this great article.

Code

First, my solution, a kind of recursive solution

public void flatten(TreeNode root) {
    helper(new TreeNode(1234), root);
}

private TreeNode helper(TreeNode pre, TreeNode node) { 
    // pre cannot be null, this function return the last node of the flatten list
    if (node == null) return pre;
    pre.left = null;
    pre.right = node;
    TreeNode a = node.left;
    TreeNode b = node.right;
    TreeNode temp = helper(node, a);
    return helper(temp, b);
}

Second, standard recursive solution

public void flatten(TreeNode root) {
    helper(root);
}

private TreeNode helper(TreeNode root) {
    if (root == null) return null;
    TreeNode oldRight = root.right;
    if (root.left != null) {
        root.right = root.left;
        // I missed this line of code: 
        root.left = null;
        root = helper(root.right);
    }
    if (oldRight != null) {
        root.right = oldRight;
        root = helper(root.right);
    }
    // return value is the last element of the flatten tree
    return root;
}

Third, stack non-recursive solution

public void flatten(TreeNode root) {
    TreeNode cur = root;
    Stack<TreeNode> rtrees = new Stack<TreeNode>();
    while (cur != null) {
        while (cur.left != null) {
            if (cur.right != null)
                rtrees.push(cur.right);
            cur.right = cur.left;
            cur.left = null;
            cur = cur.right;
        }
        if (cur != null && cur.right == null && !rtrees.isEmpty()) {
            cur.right = rtrees.pop();
        }
        cur = cur.right;
    }
}

Fourth, non-stack non-recursive solution

public void flatten(TreeNode root) {
    TreeNode cur = root;
    while (cur != null) {
        if (cur.left != null) {
            if (cur.right != null) { // if we need to prune a right subtree
                TreeNode next = cur.left;
                while (next.right != null)
                    next = next.right;
                next.right = cur.right;
            }
            cur.right = cur.left;
            cur.left = null;
        }
        cur = cur.right;
    }
}

[LeetCode 124] Binary Tree Maximum Path Sum

Question

link

Given a binary tree, find the maximum path sum.

The path may start and end at any node in the tree.

For example:
Given the below binary tree,

       1
      / \
     2   3

Return 6.

Stats

Frequency 2
Difficulty 4
Adjusted Difficulty 4
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is a difficult DFS question.

The basic idea is:

When we are in a root node, how we decided to include current node in the maximum path sum? We need to calculate the depth in both subtree. In the meantime, we need to set a variable “max” to store the max path sum we found during the recursion.

Pay attention that when a subtree sum to negative value, we discard this subtree.

Otherwise, max path sum including current node is leftSum + cur.val + rightSum

Max depth of current node is (maximum value of leftSum and rightSum) + cur.val

Solution

There are 2 points that may incur problems during coding, they are:

  1. The recursive solution is calculating the ‘max height’ while checking ‘its own max path sum’. It’s a bit hard to explain. Basic idea is doing 2 things at same time, while traversing (are one result is returned, another is stored in a public variable, at least for code 1 below).

  2. Note that ‘max height’ of a child branch can be nagative. In this case, WE MUST SET IT TO 0.

The first point is a concept seen in previous questions, but I forgot which. The second point is important, because I missed this and got failed a few times without any clue.

Code

First, recursive solution

int max = Integer.MIN_VALUE;

public int maxPathSum(TreeNode root) {
    maxHeight(root);
    return max;
}

private int maxHeight(TreeNode node) {
    if (node == null) return 0;
    int l = Math.max(0, maxHeight(node.left));
    int r = Math.max(0, maxHeight(node.right));
    max = Math.max(max, l + r + node.val);
    return node.val + Math.max(l, r);
}

Second, previous code refactored.

I removed the global variable, and instead pass an array as reference in the recursion. In this way the ‘shared’ array achieve same functionality as a global variable.

public int maxPathSum(TreeNode root) {
    int[] shared = {Integer.MIN_VALUE};
    maxHeight(root, shared);
    return shared[0];
}

private int maxHeight(TreeNode node, int[] shared) {
    if (node == null) return 0;
    int l = Math.max(0, maxHeight(node.left, shared));
    int r = Math.max(0, maxHeight(node.right, shared));
    shared[0] = Math.max(shared[0], l + r + node.val);
    return node.val + Math.max(l, r);
}

Updated on June 10th, it’s a terrible practise to use global variable. The new solution suggested by Mr. Huang uses a new ResultType Class to solve the problem.

private class ResultType {
    int singlePath, maxPath;
    ResultType(int singlePath, int maxPath) {
        this.singlePath = singlePath;
        this.maxPath = maxPath;
    }
}

public int maxPathSum(TreeNode root) {
    ResultType result = helper(root);
    return result.maxPath;
}

private ResultType helper(TreeNode node) {
    // null case
    if (node == null) {
        return new ResultType(0, Integer.MIN_VALUE);
    }
    // divide
    ResultType ll = helper(node.left);
    ResultType rr = helper(node.right);
    // conquer
    int curSinglePath = Math.max(0, node.val + 
            Math.max(ll.singlePath, rr.singlePath));
    int childMaxPath = Math.max(ll.maxPath, rr.maxPath);
    int curMaxPath = Math.max(childMaxPath, node.val + 
            ll.singlePath + rr.singlePath);
    // done
    return new ResultType(curSinglePath, curMaxPath);
}

[LeetCode 123] Best Time to Buy and Sell Stock III

Question

link

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

Stats

Frequency 1
Difficulty 4
Adjusted Difficulty 4
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is much harder to solve then previous 2 question combined.

Solution

The best solution is explained in this blog.

we can just dived the whole prices array at every point, try to calculate max profit from left and from right respectively…

However, there are many repeat calculations. So we can apply DP to record max profits for each left side and right side. then add them together at each point.

A detailed illustration with examples can be found here.

However, the coding part is slightly difficult at least for me. I made a terrible mistake with the variable ‘leftMin’ and ‘rightMax’. I declared it as ‘rightMin’ instead, and then the program logic is wrong. We should avoid this kind of mistakes.

Code

public int maxProfit(int[] prices) {
    int len = prices.length;
    if (len == 0) return 0;
    int leftMin = Integer.MAX_VALUE, rightMax = Integer.MIN_VALUE;
    int leftPro[] = new int[len], rightPro[] = new int[len];
    for (int j = 0; j <= len - 1; j ++) {
        leftMin = Math.min(leftMin, prices[j]);
        if (j == 0) leftPro[0] = 0;
        else leftPro[j] = Math.max(leftPro[j-1], prices[j] - leftMin);
    }
    for (int k = len - 1; k >= 0; k --) {
        rightMax = Math.max(rightMax, prices[k]);
        if (k == len - 1) rightPro[len-1] = 0;
        else rightPro[k] = Math.max(rightPro[k+1], rightMax - prices[k]);
    }
    int totalPro = 0;
    for (int i = 0; i < len; i ++) {
        totalPro = Math.max(totalPro, leftPro[i] + rightPro[i]);
    }
    return totalPro;
}

[LeetCode 122] Best Time to Buy and Sell Stock II

Question

link

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

Stats

Frequency 1
Difficulty 3
Adjusted Difficulty 1
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Solution

This solution is best explained here.

就从左到右轮一遍,遇到递增的都给加起来呗。

Code

public int maxProfit(int[] prices) {
    int len = prices.length;
    if (len <= 1) return 0;
    int profit = 0;
    for (int i = 1; i < len; i ++) 
        profit += Math.max(0, prices[i] - prices[i-1]);
    return profit;
}

[LeetCode 121] Best Time to Buy and Sell Stock

Question

link

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Stats

Frequency 1
Difficulty 2
Adjusted Difficulty 2
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is a question from a MIT course, Hacking a Google Interview.

I did not solve it, but it’s actually very simple to do.

Solution

The solution is explained here:

To solve this problem efficiently, you would need to track the minimum value’s index. As you traverse, update the minimum value’s index when a new minimum is met. Then, compare the difference of the current element with the minimum value. Save the buy and sell time when the difference exceeds our maximum difference (also update the maximum difference).

Time is O(N)

Code

public int maxProfit(int[] prices) {
    int len = prices.length;
    if (len <= 1) return 0;
    int min = prices[0];
    int max = Integer.MIN_VALUE;
    for (int i = 1; i < len; i ++) {
        max = Math.max(max, prices[i] - min);
        min = Math.min(min, prices[i]);
    }
    return Math.max(max, 0);
}

[LeetCode 120] Triangle

Question

link

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.

Stats

Frequency 1
Difficulty 3
Adjusted Difficulty 1
Time to use --------

Ratings/Color = 1(white) 2(lime) 3(yellow) 4/5(red)

Analysis

This is a math question.

Solution

It’s an easy question. Instead of normal DP transition function, this one is so-called bottom-up approach.

Code

public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
    int len = triangle.size();
    if (len == 0) return 0;
    int[] m = new int[len];
    m[0] = triangle.get(0).get(0);
    for (int i = 1; i < len; i ++) {
        ArrayList<Integer> cur = triangle.get(i);
        for (int j = i; j >= 0; j --) {
            if (j == i) m[j] = m[j-1] + cur.get(j);
            else if (j == 0) m[j] = m[0] + cur.get(0);
            else m[j] = Math.min(m[j-1], m[j]) + cur.get(j);
        }
    }
    int min = Integer.MAX_VALUE;
    for (Integer k: m)
        min = Math.min(min, k);
    return min;
}