Thursday, June 16, 2016

132. Palindrome Partitioning II

We need to maintain two DP states:
1. palindrome[i][j], which means if s[i, j] is a palindrome.
2. dp[i], which means minimum cuts for s[i, n-1].

First of all, dp[i] will be initialized to n-1-i as if there is no palindrome in s[i, n-1], we need to cut at every character.

Then, we need to scan from i to n-1 and see if any palindrome exists before, i.e. s[i] == s[j] and palindrome[i+1][j-1] is true. If we find the palindrome at the end, i.e. j == n-1, we don't need to cut, i.e. d[i] = 0, else if we find the palindrome at j, then we need to cut at j, i.e. we have substring s[i, j] and s[j+1, n-1]. Therefore, to get the minimum cut at i, we just need to get the min(d[i], d[j+1]+1). Also, don't forget to make palindrome[i][j] true.

1:  class Solution {  
2:  public:  
3:    int minCut(string s) {  
4:      int n = s.size();  
5:      vector<vector<bool>> palindrome(n, vector<bool>(n, false));  
6:      vector<int> dp(n, 0);  
7:      for (int i = n-1; i >= 0; i--) {  
8:        dp[i] = n-1-i;  
9:        for (int j = i; j < n; j++) {  
10:          if (s[i] == s[j] && (j-i<2 || palindrome[i+1][j-1] == true)) {  
11:            palindrome[i][j] = true;  
12:            if (j == n-1) dp[i] = 0;  
13:            else dp[i] = min(dp[i], 1+dp[j+1]);  
14:          }  
15:        }  
16:      }  
17:      return dp[0];  
18:    }  
19:  };  

Wednesday, June 15, 2016

152. Maximum Product Subarray

The idea behind is to keep two variables. One is to hold the maximum product and the other is to hold the minimum product. If there is a positive number, the maximum product gets larger and the minimum product gets smaller. On the other hand, fi there is a negative number, the maximum product and the minimum product will be swapped.

1:  class Solution {  
2:  public:  
3:    int maxProduct(vector<int>& nums) {  
4:      int max_product = nums[0];  
5:      int min_product = nums[0];  
6:      int ret = nums[0];  
7:      for (int i = 1; i < nums.size(); i++) {  
8:        if (nums[i] >= 0) {  
9:          max_product = max(max_product*nums[i], nums[i]);  
10:          min_product = min(min_product*nums[i], nums[i]);  
11:        } else {  
12:          int tmp = max_product;  
13:          max_product = max(min_product*nums[i], nums[i]);  
14:          min_product = min(tmp*nums[i], nums[i]);  
15:        }  
16:        ret = max(max_product, ret);  
17:      }  
18:      return ret;  
19:    }  
20:  };  

174. Dungeon Game

This is very similar to "Minimum Path Sum" problem. The trick here is to calculate the health in the beginning we need start from bottom-right to top-left so that we can greedily minimize the health. Also, after calculation, if there is more health left, we only need to put the minimum number 1 in that state.

1:  class Solution {  
2:  public:  
3:    int calculateMinimumHP(vector<vector<int>>& dungeon) {  
4:      int rows = dungeon.size();  
5:      int cols = dungeon[0].size();  
6:      vector<vector<int>> dp(rows+1, vector<int>(cols+1, INT_MAX));  
7:      dp[rows-1][cols] = 1;  
8:      dp[rows][cols-1] = 1;  
9:      for (int i = rows-1; i >= 0; i--) {  
10:        for (int j = cols-1; j >= 0; j--) {  
11:          int hp = min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j];  
12:          dp[i][j] = (hp <= 0) ? 1 : hp;  
13:        }  
14:      }  
15:      return dp[0][0];  
16:    }  
17:  };  

188. Best Time to Buy and Sell Stock IV

There are two kinds of algorithms to solve this problem. First one is to use MAX_HEAP and STACK.

The basic idea is to find all adjacent bottom/top paris and compute the profits from the pairs (top - bottom). we get k largest profits as the result. There are three cases:

Case 1:
When there are two adjacent pairs of bottom/top, i.e. [b1, t1] and [b2, t2] satisfying b1 > b2 (no matter of top). Obviously, there need to be two transactions to achieve the max profit. And if only one transaction allowed, the answer will be in either [b1, t1] or [b2, t2] (no merge is required because b1 > b2 then t2-b2 > t2 - b1).

Case 2:
When there are two adjacent pairs of bottom/top, i.e. [b1, t1] and [b2, t2] satisfying b1 <= b2 and t1 > t2. In this case, there need to be two transactions to achieve the max profit. HOWEVER, in case of "Case 3" following, where pairs will be merged backward, we need to push the pairs in stack and compute profits later.

Case 3:
When there are two adjacent pairs of bottom/top, i.e. [b1, t1] and [b2, t2] satisfying b1 <= b2 and t1 <= t2, we can either make one transaction by [b1, t2] or two transactions by [b1, t1] and [b2, t2]. Note, it's impossible for t1 <= b2 as [b1, t1] and [b2, t2] are adjacent each other and t1 is a top which means t1 > b2.  The trick here is to treat [t1, b2] as first transaction and [b1, t2] as second transaction. By doing so, profit [t1, b2] will be saved in max heap and the two bottom/top pair [b1, t1] and [b2, t2] will be merged to [b1, t2]. This procedure will be repeated until there is no pairs in stack. At the end the profit of [b1, t2] will be stocked in max heap.

Some examples:
[3, 9, 4, 8, 5, 9]
Round1:
Stack = [(3, 9)], Profit = []
Round 2 (Case 2):
Stack = [(3, 9), (4, 8)], Profit = []
Round 3 (Case 3):
loop 1: Stack = [(3, 9)], Profit = [3], merged pair (4, 9)
loop 2: Stack = [], Profit = [5, 3], merged pair (3, 9)
Stack = [(3, 9)],  Profit = [5, 3]
In the end, Profit = [6, 5, 3]

1:  class Solution {  
2:  public:  
3:    int maxProfit(int k, vector<int>& prices) {  
4:      int n = prices.size();  
5:      if (n == 0) return 0;  
6:      stack<pair<int, int>> stk;  
7:      priority_queue<int> profits;  
8:      int bottom = 0, top = 0;  
9:      int sum = 0;  
10:      while (top < n) {  
11:        // search for next bottom and top  
12:        for (bottom = top; bottom < n-1 && prices[bottom] > prices[bottom+1]; bottom++);  
13:        for (top = bottom+1; top < n && prices[top-1] <= prices[top]; top++);  
14:        // CASE 1: current bottom is lower than the bottom of last adjacent interval  
15:        while (!stk.empty() && prices[bottom] < prices[stk.top().first]) {  
16:          profits.push(prices[stk.top().second-1] - prices[stk.top().first]);  
17:          stk.pop();  
18:        }  
19:        while (!stk.empty() && prices[bottom] >= prices[stk.top().first] && prices[top-1] >= prices[stk.top().second-1]) {  
20:          profits.push(prices[stk.top().second-1] - prices[bottom]);  
21:          bottom = stk.top().first;  
22:          stk.pop();  
23:        }  
24:        stk.push(pair<int, int>(bottom, top));  
25:      }  
26:      while (!stk.empty()) {  
27:        profits.push(prices[stk.top().second-1] - prices[stk.top().first]);  
28:        stk.pop();  
29:      }  
30:      for (int i = 0; i < k && !profits.empty(); i++) {  
31:        sum += profits.top();  
32:        profits.pop();  
33:      }  
34:      return sum;  
35:    }  
36:  };  

Sunday, June 12, 2016

213. House Robber II

Same idea with House Robber I. We only need to do the dp twice for two different cases.

Case 1: the thief robs the first house and has to skip the last house, i.e. 0 - (n-2).

Case 2: the thief robs the second house and is able to rob the last house too. i.e. 1 - (n-1).

dp[i] indicates the maximum treasure the thief can collect at house i.

1:  class Solution {  
2:  public:  
3:    int rob(vector<int>& nums) {  
4:      int n = nums.size();  
5:      if (n == 0) return 0;  
6:      if (n == 1) return nums[0];  
7:      if (n == 2) return max(nums[0], nums[1]);  
8:      vector<int> sum1(n, 0);  
9:      vector<int> sum2(n, 0);  
10:      sum1[0] = nums[0];  
11:      sum1[1] = sum1[0];  
12:      for (int i = 2; i < n-1; i++) {  
13:        sum1[i] = max(sum1[i-2]+nums[i], sum1[i-1]);  
14:      }  
15:      sum2[1] = nums[1];  
16:      for (int i = 2; i < n; i++) {  
17:        sum2[i] = max(sum2[i-2]+nums[i], sum2[i-1]);  
18:      }  
19:      return max(sum1[n-2], sum2[n-1]);  
20:    }  
21:  };  

Note, we only care of two previous state, i.e. sum[i-2] and sum[i-1], so only two variables are needed. The space can be reduced to O(1).

221. Maximal Square

Let dp[i][j] be the maximal size of square that can be reached at matrix[i][j]. Let's have a look at some examples to get a sense of how to achieve the state equation:

matrix1 = [ [1, 1, 0], [1, 1, 1], [1, 1, 1] ]
matrix2 = [ [1, 1, 1], [1, 1, 1], [1, 0, 1] ]
matrix3 = [ [0, 1, 1], [1, 1, 1], [1, 1, 1]]

You'll see that dp[2][2] in all of these three matrixes is 2. For matrix1, it is the dp[1][2] who is 1 that restricts dp[2][2]. For matrix2, it is the dp[2][1] and while for matrix3, it is the dp[1][1]. Therefore, for dp[i][j], we have dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1.

1:  class Solution {  
2:  public:  
3:    int maximalSquare(vector<vector<char>>& matrix) {  
4:      if (matrix.size() == 0 || matrix[0].size() == 0) return 0;  
5:      int rows = matrix.size();  
6:      int cols = matrix[0].size();  
7:      vector<vector<int>> dp(rows, vector<int>(cols, 0));  
8:      int maximum = 0;  
9:      for (int i = 0; i < rows; i++) {  
10:        if (matrix[i][0] == '1') {  
11:          dp[i][0] = 1;  
12:          maximum = max(maximum, dp[i][0]);  
13:        }  
14:      }  
15:      for (int j = 0; j < cols; j++) {  
16:        if (matrix[0][j] == '1') {  
17:          dp[0][j] = 1;  
18:          maximum = max(maximum, dp[0][j]);  
19:        }  
20:      }  
21:      for (int i = 1; i < rows; i++) {  
22:        for (int j = 1; j < cols; j++) {  
23:          if (matrix[i][j] == '1') {  
24:            dp[i][j] = min(dp[i-1][j], min(dp[i][j-1], dp[i-1][j-1])) + 1;  
25:            maximum = max(maximum, dp[i][j]);  
26:          }  
27:        }  
28:      }  
29:      return maximum*maximum;  
30:    }  
31:  };  

264. Ugly Number II

An ugly number must be multiplied by 2, 3 or 5 from a smaller ugly number.

For example,
1 --> 2, 3, 5
2 --> 4, 6, 10
3 --> 6, 9, 15

From the example above, we see that there are three lists as columns. All we need to do is to merge the three lists. Let dp[i] be the ith ugly number. We keep three pointers for each list and only move the pointer when the element in the list has been merged into the result. Therefore, dp[i] = min(2*dp[p2], 3*dp[p3], 5*dp[p5]), and move p(2/3/5) to next when 2*dp[p(2/3/5)] has been chosen as dp[i].

1:  class Solution {  
2:  public:  
3:    int nthUglyNumber(int n) {  
4:      vector<int> dp(n, INT_MAX);  
5:      int p2 = 0, p3 = 0, p5 = 0;  
6:      dp[0] = 1;  
7:      for (int i = 1; i < n; i++) {  
8:        dp[i] = min(2*dp[p2], min(3*dp[p3], 5*dp[p5]));  
9:        if (dp[i] == 2*dp[p2]) p2++;  
10:        if (dp[i] == 3*dp[p3]) p3++;  
11:        if (dp[i] == 5*dp[p5]) p5++;  
12:      }  
13:      return dp[n-1];  
14:    }  
15:  };