Saturday, July 2, 2016

139. Word Break

My intuition is simple DFS.

1:  class Solution {  
2:  public:  
3:    bool wordBreak(string s, unordered_set<string>& wordDict) {  
4:      if (s.size() == 0) return true;  
5:      for (int i = 1; i <= s.size(); i++) {  
6:        if (wordDict.count(s.substr(0, i)) != 0 && wordBreak(s.substr(i), wordDict)) return true;  
7:      }  
8:      return false;  
9:    }  
10:  };  

However, this simple solution gets TLE. Why TLE? Let's look at an easy example,
"aaab", ["a", "aa", "aaa"]
1st round loop, we'll find "a" to be true until "b".
2nd round, we'll check if "aa" is in dictionary. Though it's in dictionary, we already know that "aa" can be word break into two "a". If we can memorize the word break result from 1st round loop, we don't have to check the dictionary which save a lot cost. So we can do it by memorization.

1:  class Solution {  
2:  public:  
3:    bool wordBreak(string s, unordered_set<string>& wordDict) {  
4:      vector<bool> dp(s.size(), false);  
5:      helper(s, wordDict, dp, 0);  
6:      return dp[s.size()-1];  
7:    }  
8:    void helper(string s, unordered_set<string> &wordDict, vector<bool> &dp, int start) {  
9:      if (start == s.size() || dp[start]) return;  
10:      for (int i = start; i < s.size(); i++) {  
11:        string ss = s.substr(start, i-start+1);  
12:        if (wordDict.count(s.substr(start, i-start+1)) != 0) {  
13:          dp[i] = true;  
14:          helper(s, wordDict, dp, i+1);  
15:        }  
16:      }  
17:    }  
18:  };  

Of course, inspired by memorization, it can be solved by DP. Let dp[i] be that there is valid word break sequence ending at position i.

1:  class Solution {  
2:  public:  
3:    bool wordBreak(string s, unordered_set<string>& wordDict) {  
4:      vector<bool> dp(s.size()+1, false);  
5:      dp[0] = true;  
6:      for (int i = 1; i <= s.size(); i++) {  
7:        for (int j = i-1; j >= 0; j--) {  
8:          if (dp[j]) {  
9:            if (wordDict.count(s.substr(j, i-j)) != 0) {  
10:              dp[i] = true;  
11:              break;  
12:            }  
13:          }  
14:        }  
15:      }  
16:      return dp[s.size()];  
17:    }  
18:  };  

306. Additive Number

Let's observe the example first, "112358".
1st round, 1+1 = 2
2nd round, 1 + 2 = 3
3rd round, 2 + 3 = 5
4th round, 3 + 5 = 8

And let's observe the other example, "199100199"
1st round 1+ 9 = 10
2nd round 1+ 99 = 100
3rd round 99 + 100 = 199

What do you see? If there is an additive number in the substring, the additive number becomes the second number as input for next round. And we continue doing this until the following substring doesn't include the additive number or the following substring is right the additive number. Therefore, this can be done by recursion. Particularly, when calculating the sum, we should calculate by string instead of integer to avoid overflow.

1:  class Solution {  
2:  public:  
3:    bool isAdditiveNumber(string num) {  
4:      int n = num.size();  
5:      if (n < 3) return false;  
6:      for (int i = 1; i <= n/2; i++) {  
7:        for (int j = 1; j <= (n-i)/2; j++) {  
8:          if (validate(num.substr(0,i), num.substr(i,j), num.substr(i+j))) return true;  
9:        }  
10:      }  
11:      return false;  
12:    }  
13:    bool validate(string n1, string n2, string ns) {  
14:      if ((n1.size() > 1 && n1[0] == '0') || (n2.size() > 1 && n2[0] == '0')) return false;  
15:      string sum = add(n1, n2);  
16:      if (sum == ns) return true;  
17:      if (sum.size() > ns.size()) return false;  
18:      string ss = ns.substr(0, sum.size());  
19:      if (ss == sum) return validate(n2, sum, ns.substr(sum.size()));  
20:      else return false;  
21:    }  
22:    string add(string n1, string n2) {  
23:      int i = n1.size()-1, j = n2.size()-1, carry = 0;  
24:      string res;  
25:      while (i >= 0 && j >= 0) {  
26:        int sum = n1[i--]-'0' + n2[j--]-'0'+carry;  
27:        carry = sum / 10;  
28:        res.push_back(sum%10+'0');  
29:      }  
30:      while (i >= 0) {  
31:        int sum = n1[i--]-'0'+carry;  
32:        carry = sum / 10;  
33:        res.push_back(sum%10+'0');  
34:      }  
35:      while (j >= 0) {  
36:        int sum = n2[j--]-'0'+carry;  
37:        carry = sum / 10;  
38:        res.push_back(sum%10+'0');  
39:      }  
40:      if (carry) res.push_back(carry+'0');  
41:      reverse(res.begin(), res.end());  
42:      return res;  
43:    }  
44:  };  

209. Minimum Size Subarray Sum

We can have two pointers. One scans the array and the other keeps the start position of a subarray that is supposed to have sum larger or equal to the target. One the subarray's sum is larger or equal to the target, we save the length of subarray and move the start point forward until we reach the position that the sum of the subarray is less than the target. After that, we continue scanning the array again. The total running time will be O(2n).

1:  class Solution {  
2:  public:  
3:    int minSubArrayLen(int s, vector<int>& nums) {  
4:      int start = 0, minLen = INT_MAX, sum = 0;  
5:      for (int i = 0; i < nums.size(); i++) {  
6:        sum += nums[i];  
7:        while (s <= sum) {  
8:          minLen = min(minLen, i-start+1);  
9:          sum -= nums[start++];  
10:        }  
11:      }  
12:      return minLen == INT_MAX ? 0 : minLen;  
13:    }  
14:  };  

229. Majority Element II

For this problem, we need to extend Boyer-Moore Majority Vote algorithm a little bit. Instead of keeping one number, we need to keep two numbers. If first time to see a number (i.e. either count for number 1 or count for number 2 is 0), we save that number. If we see the saved number again, count one more. If we a new number isn't seen before, we reduce the count for both saved numbers (i.e. cancel one appearance for each). Eventually, we'll have two majority numbers. Then we check if these two majority numbers appear more than third n times. Therefore, the total running time is O(2n).

1:  class Solution {  
2:  public:  
3:    vector<int> majorityElement(vector<int>& nums) {  
4:      int n1 = 0, n2 = 0, c1 = 0, c2 = 0;  
5:      vector<int> res;  
6:      for (int n : nums) {  
7:        if (n == n1) c1++;  
8:        else if (n == n2) c2++;  
9:        else if (!c1) { n1 = n; c1++; }  
10:        else if (!c2) { n2 = n; c2++; }  
11:        else { c1--; c2--;}  
12:      }  
13:      c1 = 0; c2 = 0;  
14:      for (int n : nums) {  
15:        if (n == n1) c1++;  
16:        else if (n == n2) c2++;  
17:      }  
18:      if (c1 > nums.size() / 3) res.push_back(n1);  
19:      if (c2 > nums.size() / 3) res.push_back(n2);  
20:      return res;  
21:    }  
22:  };  

187. Repeated DNA Sequences

My intuition of this problem is pattern match. However, after reading the problem more carefully, I realized it can be done by assistance of hash table. What I do is to keep a 10 letters window and then move the window to right.

1:  class Solution {  
2:  public:  
3:    vector<string> findRepeatedDnaSequences(string s) {  
4:      unordered_map<string, int> map;  
5:      vector<string> res;  
6:      if (s.size() < 10) return res;  
7:      for (int i = 0; i <= s.size()-10; i++) {  
8:        string ss = s.substr(i, 10);  
9:        if (map.find(ss) == map.end()) map[ss] = 1;  
10:        else if (map[ss] == 1) {  
11:          res.push_back(ss);  
12:          map[ss]++;  
13:        };  
14:      }  
15:      return res;  
16:    }  
17:  };  

This can pass but the performance is not very good (beat 31.4%).  Let's look at the hex number of ASCII code for 'A' (0x41), 'C' (0x43), 'G' (0x47) and 'T' (0x54). It's easy to see the last 3 bits for these four letters are different (subtract letters by multiple of 16), where A is 001, C is 010, G is 111 and T is 110. Considering the string length is only 10 letters long, so the hash key can be represented by an integer which has 32 bits in total.

1:  class Solution {  
2:  public:  
3:    vector<string> findRepeatedDnaSequences(string s) {  
4:      unordered_map<int, int> map;  
5:      vector<string> res;  
6:      if (s.size() < 10) return res;  
7:      int key = 0, i = 0;  
8:      while (i < 9) {  
9:        key = key << 3 | s[i++] & 0x7;  
10:      }  
11:      while (i < s.size()) {  
12:        key = key << 3 & 0x3FFFFFFF | s[i++] & 0x7;  
13:        if (map[key]++ == 1) {  
14:          res.push_back(s.substr(i-10, 10));  
15:        }  
16:      }  
17:      return res;  
18:    }  
19:  };  

Friday, July 1, 2016

31. Next Permutation

This is more a math problem. The idea is to inspired by the case that the sequence is in reverse order. In that case we need to reverse the whole sequence to get the next permutation. By observing this, we can see that the next sequence will be to find the reverse order subsequence and reverse that sequence first. After reversing, we need to do one more extra step, i.e. swap the pivot who breaks the reverse order with the first number that is larger than the pivot in the new subsequence.

The algorithm is
(1) In reverse order, find the first number such that nums[i] < nums[i+1]. We say this number as pivot.
(2) Reverse the nums behind the pivot, i.e. nums.begin()+i+1 to nums.end();
(3) Find the first number that is larger than pivot.
(4) swap pivot with this number.

Example:
1,2,4,5,3
(1) Find pivot is 4.
(2) Reverse the numbers behind pivot, i.e. 1,2,4,3,5
(3) The first number that is larger than pivot is 5
(4) swap them, i.e. 1,2,5,3,4
Note, if the sequence is 3,2,1, we only need to reverse the array.

1:  class Solution {  
2:  public:  
3:    void nextPermutation(vector<int>& nums) {  
4:      if (nums.empty()) return;  
5:      int i = 0, j = 0;  
6:      for (i = nums.size() - 2; i >= 0 ; i--) {  
7:        if (nums[i] < nums[i+1]) break;  
8:      }  
9:      reverse(nums.begin()+i+1, nums.end());  
10:      if (i == -1) return;  
11:      for (j = i+1; j < nums.size(); j++) {  
12:        if (nums[j] > nums[i]) break;  
13:      }  
14:      swap(nums[i], nums[j]);  
15:    }  
16:  };  

310. Minimum Height Trees

Since this undirected graph has tree characteristics, any node in the graph can be a root and the graph mustn't have cycle. Since every node can be a root, the root with minimum height must have maximum leaves. So the problem becomes trimming the leaves level by level. The intuition about the level by level traversal is BFS. And yes, this problem can be solved by BFS.

However, when implementing, there are many places that are easy to make mistakes. I've highlighted them with red.

1:  class Solution {  
2:  public:  
3:    vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {  
4:      vector<unordered_set<int>> adj(n);  
5:      vector<int> cur;  
6:      for (int i = 0; i < edges.size(); i++) {  
7:        adj[edges[i].first].insert(edges[i].second);  
8:        adj[edges[i].second].insert(edges[i].first);  
9:      }  
10:      for (int i = 0; i < n; i++) {  
11:        if (adj[i].size() == 1) {  
12:          cur.push_back(i);  
13:        }  
14:      }  
15:      if (n == 1) {  
16:        cur.push_back(0);  
17:        return cur;  
18:      }  
19:      while (true) {  
20:        vector<int> next;  
21:        for (int i : cur) {  
22:          for (int j : adj[i]) {  
23:            adj[j].erase(i);  
24:            if (adj[j].size() == 1) next.push_back(j);  
25:          }  
26:        }  
27:        if (next.empty()) break;  
28:        cur = next;  
29:      }  
30:      return cur;  
31:    }  
32:  };  

(1) this is an undirected graph, the adjacent list should reflect this.
(2) To check if a node is a leaf, we need to check the size of unordered_set is 1 (not 0) because the set should only include its parent.
(3) It's better to use for loop in the way above as it's more clear and also a convention to iterate unordered_set.