Showing posts with label backtracking. Show all posts
Showing posts with label backtracking. Show all posts

Wednesday, August 17, 2016

267. Palindrome Permutation I

The trick here as the hint says is we only need to track the first half string. So we need to count the number for the first half string. Also, we need to keep the middle character if the string has an odd length. And finally, we need to use hashtable to store each character’s count. After that, the problem becomes a classic backtracking permutation problem.

I used vector as hash table in first place. But I got TLE. I guess it’s too cost to check for 256 characters every time. Also when doing unordered_map, the iterator it in the loop “for (auto it : mp)” is a copy not the real iterator itself. So if you update this copy, it doesn’t change the content of the unordered_map at all. So we need to do either “for (auto &it : mp)” or “for (auto it=mp.begin(); it != mp.end(); i++)”.

1:  class Solution {  
2:  public:  
3:    vector<string> generatePalindromes(string s) {  
4:      unordered_map<char, int> mp;  
5:      vector<string> res;  
6:      for (int i = 0; i < s.size(); i++) {  
7:        mp[s[i]]++;  
8:      }  
9:      int odd = 0, len = 0;  
10:      string mid = "";  
11:      for (auto it = mp.begin(); it != mp.end(); it++) {  
12:        if (it->second & 1) { odd++; mid += it->first; }  
13:        it->second /= 2;  
14:        len += it->second;  
15:      }  
16:      if (odd > 1) return res;  
17:      gen(mp, len, mid, "", res);  
18:      return res;  
19:    }  
20:    void gen(unordered_map<char, int> &mp, int len, string &mid, string s, vector<string> &res) {  
21:      if (s.size() == len) {  
22:        string r = s;  
23:        reverse(r.begin(), r.end());  
24:        res.push_back(s+mid+r);  
25:        return;  
26:      }  
27:      for (auto it = mp.begin(); it != mp.end(); it++) {  
28:        if (it->second > 0) {  
29:          it->second--;  
30:          gen(mp, len, mid, s+it->first, res);  
31:          it->second++;  
32:        }  
33:      }  
34:    }  
35:  };  

Sunday, August 14, 2016

254. Factor Combinations

Backtracking solution. A trick I played here is the scanning starts from last number of candidate solution to guarantee that the solution is in an ascending order and thus duplicate is avoided.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> getFactors(int n) {  
4:      vector<int> sol;  
5:      vector<vector<int>> res;  
6:      helper(n, sol, res);  
7:      return res;  
8:    }  
9:    void helper(int n, vector<int> sol, vector<vector<int>> &res) {  
10:      for (int i = sol.empty() ? 2 : sol.back(); i*i <= n ; i++) {  
11:        if (n % i == 0) {  
12:          int a = n / i;  
13:          sol.push_back(i);  
14:          sol.push_back(a);  
15:          res.push_back(sol);  
16:          sol.pop_back();  
17:          helper(a, sol, res);  
18:          sol.pop_back();  
19:        }  
20:      }  
21:    }  
22:  };  

Wednesday, August 3, 2016

126. Word Ladder II

I built my solution on top of problem 127 "Word Ladder". I use a hash map to store the prior string set of current string. The key is current string and the value is the prior string set. The reason I use unordered_set for prior strings is to avoid duplicates. The basic idea is to find the shortest transformation sequence first and then build the sequences by the hash map.

1:  class Solution {  
2:  private:  
3:    unordered_map<string, unordered_set<string>> mp;  
4:    queue<string> q;  
5:    vector<vector<string>> res;  
6:    vector<string> path;  
7:    int dist;  
8:  public:  
9:    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {  
10:      dist = helper(start, end, dict);  
11:      if (dist) output(start, end);  
12:      return res;   
13:    }  
14:    int helper(string &start, string &end, unordered_set<string> &dict) {  
15:      dict.insert(start);  
16:      q.push(start);  
17:      path.push_back(end);  
18:      dist = 1;  
19:      while (!q.empty()) {  
20:        int n = q.size();  
21:        for (int i = 0; i < n; i++) {  
22:          dict.erase(q.front());  
23:          q.push(q.front());  
24:          q.pop();  
25:        }  
26:        for (int i = 0; i < n; i++) {  
27:          string word = q.front();  
28:          q.pop();  
29:          if (word == end) return dist;  
30:          addNeighbors(word, dict);  
31:        }  
32:        dist++;  
33:      }  
34:      return 0;  
35:    }  
36:    void addNeighbors(string word, unordered_set<string> &dict) {  
37:      string tmp = word;  
38:      for (int i = 0; i < word.size(); i++) {  
39:        char c = tmp[i];  
40:        for (int j = 0; j < 26; j++) {  
41:          tmp[i] = 'a' + j;  
42:          if (dict.count(tmp)) {  
43:            q.push(tmp);  
44:            mp[tmp].insert(word);  
45:          }  
46:        }  
47:        tmp[i] = c;  
48:      }  
49:    }  
50:    void output(string &start, string end) {  
51:      if (path.size() == dist) {  
52:        if (start == end) {  
53:          reverse(path.begin(), path.end());  
54:          res.push_back(path);  
55:          reverse(path.begin(), path.end());  
56:        }  
57:        return;  
58:      }  
59:      int n = mp[end].size();  
60:      for (auto it = mp[end].begin(); it != mp[end].end(); it++) {  
61:        string s = *it;  
62:        path.push_back(s);  
63:        output(start, s);  
64:        path.pop_back();  
65:      }  
66:    }  
67:  };  

Thursday, July 21, 2016

140. Word Break II

My intuition is to use backtracking solution directly. But it get TLE. So we definitely need to do some memorization.

1:  class Solution {  
2:  public:  
3:    vector<string> wordBreak(string s, unordered_set<string>& wordDict) {  
4:      vector<string> res;  
5:      helper(s, 0, wordDict, "", res);  
6:      return res;  
7:    }  
8:    void helper(string s, int i, unordered_set<string> &wordDict, string sol, vector<string> &res) {  
9:      if (i == s.size()) { sol.pop_back(); res.push_back(sol); return;}  
10:      for (int j = i; j < s.size(); j++) {  
11:        string word = s.substr(i, j-i+1);  
12:        if (wordDict.count(word) == 0) continue;  
13:        helper(s, j+1, wordDict, sol+word+" ", res);  
14:      }  
15:    }  
16:  };  


Let’s look at one example first. Say, we have string “aaaab”, dictionary [“a”, “aa”]. Then let’s see how the solution above works.
Step 1: “a a a a” and “b” not valid.
Setp 2: “a a a” and recursively check “ab”.
Step 3: “a a aa” and “b” not valid.
Step 4: “a a” and recursively check “aab”.
Step 5: “a aa” and recursively check “ab”.
Step 6: “aa” and recursively check “aab”.
From here, we can see in Step 5 we don’t have to recursively check “ab” again because from Step 2 we already know that “ab” is not breakable. Same to Step 6. So if we can memorize if word[i..n-1] is breakable, then we can speed up the solution. Let dp[i] be that s[i..n-1] is not breakable. And then the trick becomes how to update the dp[i]. If there is no breakable words, no new solution will be added to the result. So we can update the dp[i] upon that.

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

Wednesday, July 20, 2016

17. Letter Combinations of a Phone Number

A typical backtracking solution.

1:  class Solution {  
2:  private:  
3:    vector<string> pad = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};  
4:  public:  
5:    vector<string> letterCombinations(string digits) {  
6:      vector<string> res;  
7:      if (digits.size() == 0) return res;  
8:      helper(digits, 0, "", res);  
9:      return res;  
10:    }  
11:    void helper(string &digits, int i, string sol, vector<string> &res) {  
12:      if (i == digits.size()) {  
13:        res.push_back(sol); return;  
14:      }  
15:      for (int j = 0; j < pad[digits[i]-'0'].size(); j++) {  
16:        sol += pad[digits[i]-'0'][j];  
17:        helper(digits, i+1, sol, res);  
18:        sol.pop_back();  
19:      }  
20:    }  
21:  };  

Saturday, July 16, 2016

294. Flip Game II

The idea is to exhaust all the ways by backtracking. As long as there is a way to prevent the other from winning, we stop the backtracking.

1:  class Solution {  
2:  private:  
3:    string ss;  
4:    int len;  
5:  public:  
6:    bool canWin(string s) {  
7:      len = s.size();  
8:      ss = s;  
9:      return helper();  
10:    }  
11:    bool helper() {  
12:      for (int i = 0; i < len-1; i++) {  
13:        if (ss[i] == '+' && ss[i+1] == '+') {  
14:          ss[i] = ss[i+1] = '-'; // make the move  
15:          bool win = helper();  
16:          ss[i] = ss[i+1] = '+'; // restore the move  
17:          if (!win) return true;  
18:        }  
19:      }  
20:      return false;  
21:    }  
22:  };  

When I revisited this code, I got much conciser code:

1:  class Solution {  
2:  public:  
3:    bool canWin(string s) {  
4:      for (int i = 0; i+1 < s.size(); i++) {  
5:        if (s[i] == '+' && s[i+1] == '+') {  
6:          s[i] = s[i+1] = '-';  
7:          if (!canWin(s)) return true;  
8:          s[i] = s[i+1] = '+';  
9:        }  
10:      }  
11:      return false;  
12:    }  
13:  };  

Friday, July 15, 2016

247. Strobogrammatic Number II

Follow the tips, I realized that this problem can be solved by backtracking. I was trying to design the backtracking API to include the final result. But it turns out not working and can complicate the solution. So I followed the API of top rated solution, i.e. the backtracking function returns the result. Also, it is important to do backtracking with n-2 not n-1.

1:  class Solution {  
2:  public:  
3:    vector<string> findStrobogrammatic(int n) {  
4:      return helper(n, n);  
5:    }  
6:    vector<string> helper(int n, int m) {  
7:      if (n == 0) return vector<string> {""};  
8:      if (n == 1) return vector<string> {"0", "1", "8"};  
9:      vector<string> tmp = helper(n-2, m), res;  
10:      for (int i = 0; i < tmp.size(); i++) {  
11:        if (n != m) res.push_back("0" + tmp[i] + "0");  
12:        res.push_back("1" + tmp[i] + "1");  
13:        res.push_back("8" + tmp[i] + "8");  
14:        res.push_back("6" + tmp[i] + "9");  
15:        res.push_back("9" + tmp[i] + "6");  
16:      }  
17:      return res;  
18:    }  
19:  };  

320. Generalized Abbreviation

I observed the rule is the abbreviation number doesn't appear consecutively. So in the backtracking solution, we need to have a flag in the API to indicate that whether the previous character is an abbreviation number or not. I made some mistakes in the place in red.

1:  class Solution {  
2:  public:  
3:    vector<string> generateAbbreviations(string word) {  
4:      vector<string> res;  
5:      helper(word, "", res, 0, false);  
6:      return res;  
7:    }  
8:    void helper(string &word, string abbr, vector<string> &res, int i, bool prev) {  
9:      if (i == word.size()) {  
10:        res.push_back(abbr);  
11:        return;  
12:      }  
13:      helper(word, abbr+word[i], res, i+1, false);  
14:      if (!prev) {  
15:        for (int j = 1; i+j <= word.size(); j++)  
16:          helper(word, abbr+to_string(j), res, i+j, true);  
17:      }  
18:    }  
19:  };  

Thursday, July 14, 2016

77. Combinations

A typical backtracking solution.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> combine(int n, int k) {  
4:      vector<vector<int>> res;  
5:      vector<int> sol;  
6:      helper(n, k, 0, sol, res);  
7:      return res;  
8:    }  
9:    void helper(int n, int k, int position, vector<int> &sol, vector<vector<int>> &res) {  
10:      if (position == k) {  
11:        res.push_back(sol);  
12:        return;  
13:      }  
14:      if (position > k) {  
15:        return;  
16:      }  
17:      for (int i = sol.empty() ? 1 : sol.back()+1; i <= n; i ++) {  
18:        sol.push_back(i);  
19:        helper(n, k, position+1, sol, res);  
20:        sol.pop_back();  
21:      }  
22:    }  
23:  };  

When I revisited this problem, I had a more concise solution as following.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> combine(int n, int k) {  
4:      vector<int> sol;  
5:      vector<vector<int>> res;  
6:      helper(n, 1, k, sol, res);  
7:      return res;  
8:    }  
9:    void helper(int n, int start, int k, vector<int> sol, vector<vector<int>> &res) {  
10:      if (k == 0) { res.push_back(sol); return; }  
11:      for (int i = start; i <= n; i++) {  
12:        sol.push_back(i);  
13:        helper(n, i+1, k-1, sol, res);  
14:        sol.pop_back();  
15:      }  
16:    }  
17:  };  

Saturday, July 9, 2016

212. Word Search II

My initial idea is to use dfs for each character in the board. And for each dfs, we search the word so far in the dictionary. The dictionary is a hash table built by the input vector words. However, by the idea of trie, it'll be much faster than hash table. Note, to avoid duplicates, I use the set instead of array to hold the results.

1:  class TrieNode {  
2:  public:  
3:    TrieNode *next[26];  
4:    bool isEnd;  
5:    TrieNode() {  
6:      memset(next, 0, sizeof(next));  
7:      isEnd = false;  
8:    }  
9:  };  
10:  class Trie {  
11:  public:  
12:    TrieNode *next[26];  
13:    bool isEnd;  
14:    Trie (vector<string> &words) {  
15:      root = new TrieNode();  
16:      for (int i = 0; i < words.size(); i++) {  
17:        _insert(words[i]);  
18:      }  
19:    }  
20:    TrieNode *getRoot() {  
21:      return root;  
22:    }  
23:  private:  
24:    TrieNode *root;  
25:    void _insert(string word) {  
26:      TrieNode *p = root;  
27:      for (int i = 0; i < word.size(); i++) {  
28:        if (p->next[word[i]-'a'] == NULL) {  
29:          p->next[word[i]-'a'] = new TrieNode();  
30:        }  
31:        p = p->next[word[i]-'a'];  
32:      }  
33:      p->isEnd = true;  
34:    }  
35:  };  
36:  class Solution {  
37:  public:  
38:    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {  
39:      Trie *trie = new Trie(words);  
40:      TrieNode *root = trie->getRoot();  
41:      unordered_set<string> res;  
42:      string sol;  
43:      for (int i = 0; i < board.size(); i++) {  
44:        for (int j = 0; j < board[0].size(); j++) {  
45:          find(board, i, j, root, sol, res);  
46:        }  
47:      }  
48:      return vector<string>(res.begin(), res.end());;  
49:    }  
50:    void find(vector<vector<char>> &board, int i, int j, TrieNode *t, string sol, unordered_set<string> &res) {  
51:      if (i < 0 || j < 0 || i == board.size() || j == board[0].size() || board[i][j] == 0) return;  
52:      if (t->next[board[i][j]-'a'] != NULL) {  
53:        sol += board[i][j];  
54:        t = t->next[board[i][j]-'a'];  
55:        if (t->isEnd) res.insert(sol);  
56:        char c = board[i][j];  
57:        board[i][j] = 0;  
58:        find(board, i+1, j, t, sol, res);  
59:        find(board, i-1, j, t, sol, res);  
60:        find(board, i, j+1, t, sol, res);  
61:        find(board, i, j-1, t, sol, res);  
62:        board[i][j] = c;  
63:      }  
64:    }  
65:  };  

When I revisited this problem, I had following solution which has the same idea. I made mistakes in,
line 20: I put this line into the if clause which misses the case when the next trie node isn't null.
line 54: I was using vector first but it will include some duplicates. So I changed it to set.
line 56-58: First of all, I had line 58 before line 56-57. This results in empty output. Then, I realized that line 58 should check after we've include the new trie node (because the root in dfs() function is the parent node trie node). However, the result missed last node in a word. Then I eventually moved line 58 behind line 56-57 and get the correct result.

Also it is very easy to make mistake in line 65 to pass "root->next[board[i][j]-'a']" to the dfs function. Note at that moment, board[i][j] has been changed to 0 so you'll get segment fault.

1:  class TrieNode {  
2:  public:  
3:    bool isEnd;  
4:    vector<TrieNode *> next;  
5:    TrieNode() {  
6:      isEnd = false;  
7:      next = vector<TrieNode *>(26, NULL);  
8:    }  
9:  };  
10:  class Trie {  
11:  private:  
12:    TrieNode *root;  
13:    void _insert(string word) {  
14:      TrieNode *p = root;  
15:      for (int i = 0; i < word.size(); i++) {  
16:        int index = word[i]-'a';  
17:        if (p->next[index] == NULL) {  
18:          p->next[index] = new TrieNode();  
19:        }  
20:        p = p->next[index];  
21:      }  
22:      p->isEnd = true;  
23:    }  
24:  public:  
25:    Trie(vector<string> &words) {  
26:      root = new TrieNode();  
27:      for (int i = 0; i <words.size(); i++) {  
28:        _insert(words[i]);  
29:      }  
30:    }  
31:    TrieNode *getRoot() {  
32:      return root;  
33:    }  
34:  };  
35:  class Solution {  
36:  private:  
37:    int row, col;  
38:    vector<pair<int, int>> dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};  
39:  public:  
40:    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {  
41:      set<string> res;  
42:      row = board.size();  
43:      if (row == 0) return vector<string>();  
44:      col = board[0].size();  
45:      Trie *trie = new Trie(words);  
46:      TrieNode *root = trie->getRoot();  
47:      for (int i = 0; i < row; i++) {  
48:        for (int j = 0; j < col; j++) {  
49:          dfs(board, i, j, root, "", res);  
50:        }  
51:      }  
52:      return vector<string>(res.begin(), res.end());  
53:    }  
54:    void dfs(vector<vector<char>> &board, int i, int j, TrieNode *root, string path, set<string> &res) {  
55:      if (root->next[board[i][j]-'a'] == NULL) return;  
56:      root = root->next[board[i][j]-'a'];  
57:      path += board[i][j];  
58:      if (root->isEnd) res.insert(path);  
59:      char c = board[i][j];  
60:      board[i][j] = 0;  
61:      for (int d = 0; d < dir.size(); d++) {  
62:        int ii = i + dir[d].first;  
63:        int jj = j + dir[d].second;  
64:        if (ii < 0 || ii == row || jj < 0 || jj == col || board[ii][jj] == 0) continue;  
65:        dfs(board, ii, jj, root, path, res);  
66:      }  
67:      board[i][j] = c;  
68:    }  
69:  };  

Wednesday, July 6, 2016

52. N-Queens II

Same idea with “N-Queens”. The only difference is to increase the counter instead of pushing the solution to result.

1:  class Solution {  
2:  private:  
3:    int count;  
4:  public:  
5:    int totalNQueens(int n) {  
6:      count = 0;  
7:      vector<vector<bool>> sol(n, vector<bool>(n, true));  
8:      helper(sol, 0, n);  
9:      return count;  
10:    }  
11:    void helper(vector<vector<bool>> &sol, int row, int n) {  
12:      if (row == n) { count++; return; }  
13:      for (int j = 0; j < n; j++) {  
14:        if (valid(sol, row, j, n)) {  
15:          sol[row][j] = false;  
16:          helper(sol, row+1, n);  
17:          sol[row][j] = true;  
18:        }  
19:      }  
20:    }  
21:    bool valid(vector<vector<bool>> &sol, int row, int col, int n) {  
22:      for (int i = 0; i < row; i++) if (!sol[i][col]) return false;  
23:      for (int i = row-1, j = col-1; i >= 0 && j >=0; i--, j--) if (!sol[i][j]) return false;  
24:      for (int i = row-1, j = col+1; i >= 0 && j < n; i--, j++) if (!sol[i][j]) return false;  
25:      return true;  
26:    }  
27:  };  

51. N-Queens

We’ll backtrack row by row and for each row, we check column by column. Since it’s a backtracking solution, we don’t have to check rows that’ll be checked later. So for validation, we only need to check the characters in previous rows in a particular column and the 45 degree and 135 degree diagonals.

1:  class Solution {  
2:  public:  
3:    vector<vector<string>> solveNQueens(int n) {  
4:      vector<vector<string>> res;  
5:      vector<string> sol(n, string(n, '.'));  
6:      helper(res, sol, 0, n);  
7:      return res;  
8:    }  
9:    void helper(vector<vector<string>> &res, vector<string> &sol, int row, int n) {  
10:      if (n == row) {  
11:        res.push_back(sol);  
12:        return;  
13:      }  
14:      for (int j = 0; j < n; j++) {  
15:        if (valid(sol, row, j, n)) {  
16:          sol[row][j] = 'Q';  
17:          helper(res, sol, row+1, n);  
18:          sol[row][j] = '.';  
19:        }  
20:      }  
21:    }  
22:    bool valid(vector<string> &sol, int row, int col, int n) {  
23:      for (int i = 0; i < row; i++) {  
24:        if (sol[i][col] == 'Q') return false;  
25:      }  
26:      for (int i=row-1, j=col-1; i >= 0 && j >= 0; i--, j--) {  
27:        if (sol[i][j] == 'Q') return false;  
28:      }  
29:      for (int i=row-1, j=col+1; i >= 0 && j < n; i--, j++) {  
30:        if (sol[i][j] == 'Q') return false;  
31:      }  
32:      return true;  
33:    }  
34:  };  

Saturday, July 2, 2016

The first idea when I see this problem is backtracking to enumerate all possible permutations and pick up the kth one.

1:  class Solution {  
2:  private:  
3:    int kth;  
4:    string res;  
5:  public:  
6:    string getPermutation(int n, int k) {  
7:      string s, sol;  
8:      kth = k;  
9:      for (int i = 1; i <= n; i++) {  
10:        s += '0' + i;  
11:      }  
12:      helper(s, sol);  
13:      return res;  
14:    }  
15:    void helper(string s, string sol) {  
16:      if (s.empty()) {  
17:        kth--;  
18:        return;  
19:      }  
20:      for (int i = 0; i < s.size(); i++) {  
21:        char c = s[i];  
22:        sol += c;  
23:        s.erase(s.begin()+i);  
24:        helper(s, sol);  
25:        if (kth == 0 && res.empty()) {  
26:          res = sol;  
27:          return;  
28:        }  
29:        sol.pop_back();  
30:        s.insert(s.begin()+i, c);  
31:      }  
32:      return;  
33:    }  
34:  };  

However, I got TLE. Obviously, in order to get the sequence that problem states, the code seems to be very efficient because there is many erase and insert operation on string.

Another way is math. We know that for set [1,2,...,n], we have n! in total permutations. To get the kth permutation, we compute from the first position. So for first position, we have n * (n-1)! permutations. Thus, the first number for kth permutation should be k / (n-1)!. After fixing the first number in permutation, we have k2 = k % (n-1)! permutations left. So we'll compute the second number as k2 / (n-2)!.  We'll continue computing until we find all n numbers for kth permutation.

1:  class Solution {  
2:  public:  
3:    string getPermutation(int n, int k) {  
4:      string nums;  
5:      int permCount = 1;  
6:      string res;  
7:      for (int i = 1; i <= n; i++) {  
8:        nums += '0' + i;  
9:        permCount *= i;  
10:      }  
11:      k--; // this is to adjust the index.  
12:      for (int i = 0; i < n; i++) {  
13:        permCount /= n-i;  
14:        int index = k / permCount;  
15:        res += nums[index];  
16:        nums.erase(nums.begin()+index);  
17:        k %= permCount;  
18:      }  
19:      return res;  
20:    }  
21:  };  

Tuesday, June 28, 2016

216. Combination Sum III

This is a combination problem so the intuition is to use backtracking algorithm. A trick here is for the for loop, the start will be the last number from the result of last iteration and the end will be 9 because duplicated number is not allowed. Let's see an example why we do this way.

Input k = 3, n = 9
R1,  R2,    R3
[1], [1, 2], [1, 2, 6]
       [1, 3], [1, 3, 5]
       [1, 4...9]
[2], [2, 3], [2, 3, 4]
       [2, 4...9]
[3...9]

From the example we can see, that we should start from the last number from the result of last iteration. If we don't, then we'll introduce duplicated solutions.

1:  class Solution {  
2:  private:  
3:    vector<vector<int>> res;  
4:  public:  
5:    vector<vector<int>> combinationSum3(int k, int n) {  
6:      vector<int> sol;  
7:      helper(k, n, sol);  
8:      return res;  
9:    }  
10:    void helper(int k, int n, vector<int> &sol) {  
11:      if (k > n || (k == 0 && n != 0)) return;  
12:      if (k == 0 && n == 0) {  
13:        res.push_back(sol);  
14:        return;  
15:      }  
16:      for (int i = sol.empty() ? 1 : sol.back()+1; i < 10; i++) {  
17:        sol.push_back(i);  
18:        helper(k-1, n-i, sol);  
19:        sol.pop_back();  
20:      }  
21:      return;  
22:    }  
23:  };  

Sunday, June 26, 2016

131. Palindrome Partitioning

Typical backtracking (or DFS) problem. In each DFS function, we'll check the substring of length [1, s.size()-start+1]. The termination condition will be start == s.size().

1:  class Solution {  
2:  public:  
3:    vector<vector<string>> partition(string s) {  
4:      vector<vector<string>> res;  
5:      vector<string> sol;  
6:      helper(0, s, sol, res);  
7:      return res;  
8:    }  
9:    bool isPalindrome(string s) {  
10:      int i = 0, j = s.size()-1;  
11:      while (i < j) {  
12:        if (s[i] != s[j]) return false;  
13:        i++; j--;  
14:      }  
15:      return true;  
16:    }  
17:    void helper(int start, string s, vector<string> &sol, vector<vector<string>> &res) {  
18:      if (start == s.size()) {  
19:        res.push_back(sol);  
20:        return;  
21:      }  
22:      for (int i = start; i < s.size(); i++) {  
23:        string ss = s.substr(start, i-start+1);  
24:        if (isPalindrome(ss)) {  
25:          sol.push_back(ss);  
26:          helper(i+1, s, sol, res);  
27:          sol.pop_back();  
28:        }  
29:      }  
30:      return;  
31:    }  
32:  };  

40. Combination Sum II

Same idea as "39. Combination Sum". The only difference is to kick out duplicates.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {  
4:      sort(candidates.begin(), candidates.end());  
5:      vector<vector<int>> res;  
6:      vector<int> sol;  
7:      helper(candidates, sol, res, 0, target);  
8:      return res;  
9:    }  
10:    void helper(vector<int> &candidates, vector<int> &sol, vector<vector<int>> &res, int start, int target) {  
11:      if (target == 0) {  
12:        res.push_back(sol);  
13:        return;  
14:      }  
15:      if (target < 0) return;  
16:      for (int i = start; i < candidates.size(); i++) {  
17:        if (i > start && candidates[i] == candidates[i-1]) continue;  
18:        sol.push_back(candidates[i]);  
19:        helper(candidates, sol, res, i+1, target-candidates[i]);  
20:        sol.pop_back();  
21:      }  
22:    }  
23:  };  

When I revisited this problem, I did following way. I missed line 13 and made mistakes in
Line 22-23: I was trying to push candidates[start] to sol and call “helper(candidates, start+1, sum+candidates[start], target, sol, res)”.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {  
4:      vector<int> sol;  
5:      vector<vector<int>> res;  
6:      sort(candidates.begin(), candidates.end());  
7:      helper(candidates, 0, 0, target, sol, res);  
8:      return res;  
9:    }  
10:    void helper(vector<int> &candidates, int start, int sum, int target, vector<int> &sol, vector<vector<int>> &res) {  
11:      if (start == candidates.size()) return;  
12:      for (int i = start; i < candidates.size(); i++) {  
13:        if (i > start && candidates[i-1] == candidates[i]) continue;  
14:        if (candidates[i] + sum == target) {  
15:          sol.push_back(candidates[i]);  
16:          res.push_back(sol);  
17:          sol.pop_back();  
18:          return;  
19:        } else if (candidates[i] + sum > target) {  
20:          return;  
21:        } else {  
22:          sol.push_back(candidates[i]);  
23:          helper(candidates, i+1, sum+candidates[i], target, sol, res);  
24:          sol.pop_back();  
25:        }  
26:      }  
27:    }  
28:  };  

47. Permutations II

My solution modifies the input nums which costs a lot of performance (only beats 14%).

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> permuteUnique(vector<int>& nums) {  
4:      vector<vector<int>> res;  
5:      vector<int> sol;  
6:      sort(nums.begin(), nums.end());  
7:      helper(nums, sol, res);  
8:      return res;  
9:    }  
10:    void helper(vector<int> &nums, vector<int> &sol, vector<vector<int>> &res) {  
11:      if (nums.size() == 0) {  
12:        res.push_back(sol);  
13:        return;  
14:      }  
15:      for (int i = 0; i < nums.size(); i++) {  
16:        if (i > 0 && nums[i] == nums[i-1]) continue;  
17:        int tmp = nums[i];  
18:        sol.push_back(nums[i]);  
19:        nums.erase(nums.begin()+i);  
20:        helper(nums, sol, res);  
21:        nums.insert(nums.begin()+i, tmp);  
22:        sol.pop_back();  
23:      }  
24:    }  
25:  };  

The top voted algorithm uses swap. Node it is not passing nums as reference as recursive calls keeps swapping the elements so nums[i] is changed by the following recursive calls and you can't simply play swap again to get it nums[i] back to its original position.

When I revisited this problem, I made a mistake in
Line 5: I forget sorting the array in first place.
Line 9: I pass in a reference and I swap back after line 17. However, this generate duplicates. For example, a = [1,1,2,2]. After you swap a[0] and a[2], you get a = [2,1,1,2] and then you’ll get subsequent permutation like [2,1,2,1]. However, if you swap back a[0] and a[2], and then you’ll swap a[0] and a[3], this time you’ll get [2,1,2,1] which is a duplicate. Then the question is why just one swapping works. Because it prevent you from swapping the same number again since you have check “i != start && nums[start] == nums[i]”.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> permuteUnique(vector<int>& nums) {  
4:      vector<vector<int>> res;  
5:      sort(nums.begin(), nums.end());  
6:      helper(nums, 0, res);  
7:      return res;  
8:    }  
9:    void helper(vector<int> nums, int start, vector<vector<int>> &res) {  
10:      if (start == nums.size()) {  
11:        res.push_back(nums);  
12:        return;  
13:      }  
14:      for (int i = start; i < nums.size(); i++) {  
15:        if (i > start && nums[start] == nums[i]) continue;  
16:        swap(nums[start], nums[i]);  
17:        helper(nums, start+1, res);  
18:      }  
19:    }  
20:  };  

Saturday, June 25, 2016

90. Subsets II

Same as "78. Subsets" except checking whether a new element is a duplicate.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> subsetsWithDup(vector<int>& nums) {  
4:      sort(nums.begin(), nums.end());  
5:      vector<vector<int>> res;  
6:      vector<int> sol;  
7:      res.push_back(sol);  
8:      helper(nums, 0, sol, res);  
9:      return res;  
10:    }  
11:    void helper(vector<int> &nums, int start, vector<int> &sol, vector<vector<int>> &res) {  
12:      for (int i = start; i < nums.size(); i++) {  
13:        if (i == start || nums[i] != nums[i-1]) {  
14:          sol.push_back(nums[i]);  
15:          res.push_back(sol);  
16:          helper(nums, i+1, sol, res);  
17:          sol.pop_back();  
18:        }  
19:      }  
20:    }  
21:  };  

39. Combination Sum

A classical backtracking solution.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {  
4:      vector<vector<int>> res;  
5:      vector<int> sol;  
6:      sort(candidates.begin(), candidates.end());  
7:      helper(candidates, 0, sol, res, target);  
8:      return res;  
9:    }  
10:    void helper(vector<int> &candidates, int start, vector<int> &sol, vector<vector<int>> &res, int target) {  
11:      if (target == 0) {  
12:        res.push_back(sol);  
13:        return;  
14:      }  
15:      if (candidates[start] > target) return;  
16:      for (int i = start; i < candidates.size(); i++) {  
17:        sol.push_back(candidates[i]);  
18:        helper(candidates, i, sol, res, target-candidates[i]);  
19:        sol.pop_back();  
20:      }  
21:    }  
22:  };  

78. Subsets

Classic backtracking solution.

1:  class Solution {  
2:  public:  
3:    vector<vector<int>> subsets(vector<int>& nums) {  
4:      vector<vector<int>> res;  
5:      vector<int> sol;  
6:      res.push_back(sol);  
7:      helper(nums, 0, sol, res);  
8:      return res;  
9:    }  
10:    void helper(vector<int>& nums, int start, vector<int> &sol, vector<vector<int>> &res) {  
11:      if (start == nums.size()) return;  
12:      for (int i = start; i< nums.size(); i++) {  
13:        sol.push_back(nums[i]);  
14:        res.push_back(sol);  
15:        helper(nums, i+1, sol, res);  
16:        sol.pop_back();  
17:      }  
18:    }  
19:  };