trie
Tree structure. Each node contains:
vector<TrieNode*> children
bool isWord
API:
-
insert
: same as building the trie: traverse each word from given, take thechar
, create aTrieNode
if null and movecur
like in a LinkedList, iterate through. When it's done,flag
it as a word. search
startsWith
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); ++i) {
char c = word.charAt(i);
if (node.children[c - 'a'] == null) {
node.children[c - 'a'] = new TrieNode();
}
node = node.children[c - 'a'];
}
node.isWord = true;
}
public boolean search(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); ++i) {
char c = word.charAt(i);
if (node.children[c - 'a'] == null) return false;
node = node.children[c - 'a'];
}
return node.isWord;
}
public boolean startsWith(String prefix) {
TrieNode node = root;
for (int i = 0; i < prefix.length(); ++i) {
char c = prefix.charAt(i);
if (node.children[c - 'a'] == null) return false;
node = node.children[c - 'a'];
}
return true;
}
}
class TrieNode {
public TrieNode[] children = new TrieNode[26];
public boolean isWord;
public TrieNode() {}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
Notice that
if (node.children[c - 'a'] == null) return false;
node = node.children[c - 'a'];
"self-indexing" inside a loop until hits an end: similar to union
all nodes of the same root on one branch.
backtracking + trie: use the trie from wordSet for terminal condition. In this case, sets: wordSet
or firstChar
would not work as would trie.
word search ii
class TrieNode {
TrieNode[] children = new TrieNode[26];
String word;
}
class Solution {
public List<String> findWords(char[][] board, String[] words) {
List<String> res = new ArrayList();
int m = board.length, n = board[0].length;
TrieNode root = buildTrie(words);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
dfs(board, i, j, root, res);
}
}
return res;
}
private void dfs(char[][] board, int i, int j, TrieNode p, List<String> res) {
char c = board[i][j];
if (c == '#' || p.children[c - 'a'] == null) return;
p = p.children[c - 'a'];
if (p.word != null) {
res.add(p.word);
p.word = null; // de-duplicate
}
board[i][j] = '#';
if (i > 0) dfs(board, i-1, j, p, res);
if (j > 0) dfs(board, i, j-1, p, res);
if (i < board.length - 1) dfs(board, i+1, j, p, res);
if (j < board[0].length - 1) dfs(board, i, j+1, p, res);
board[i][j] = c;
}
public TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String w : words) {
TrieNode p = root;
for (char c : w.toCharArray()) {
int i = c - 'a';
if (p.children[i] == null) p.children[i] = new TrieNode();
p = p.children[i];
}
p.word = w;
}
return root;
}
}
Or if trie is not involved, plain backtracking:
word search
public boolean exist(char[][] board, String word) {
if (board.length == 0 || board[0].length == 0) return false;
int m = board.length, n = board[0].length;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (search(board, i, j, word, 0)) {
return true;
}
}
}
return false;
}
private boolean search(char[][] board, int i, int j, String word, int index) {
if (index == word.length()) return true;
int m = board.length, n = board[0].length;
if (i < 0 || j < 0 || i >= m || j >= n || board[i][j] != word.charAt(index))
return false;
char c = board[i][j];
board[i][j] = '#';
boolean res = false;
int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int x = i, y = j;
for (int k = 0; k < 4; ++k) {
res = res || search(board, x+dirs[k][0], y+dirs[k][1], word, index+1);
}
board[i][j] = c;
return res;
}
plain backtracking
Superb explanation. Think one possible solution out of all, as base case; then add constraints, iterate through all possible choices one time, backtrack, and retreat back to previous state.
17
class Solution {
Map<Character, String> map = new HashMap();
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList();
if (digits.length() == 0) return res;
map.put('2', "abc"); map.put('3', "def"); map.put('4', "ghi"); map.put('5', "jkl");
map.put('6', "mno"); map.put('7', "pqrs"); map.put('8', "tuv"); map.put('9', "wxyz");
StringBuilder sb = new StringBuilder();
backtrack(res, sb, digits);
return res;
}
private void backtrack(List<String> res, StringBuilder sb, String digits) {
if (sb.length() == digits.length()) {
res.add(sb.toString());
return;
}
String s = map.get(digits.charAt(sb.length()));
for (char c : s.toCharArray()) {
sb.append(c);
backtrack(res, sb, digits);
sb.deleteCharAt(sb.length() - 1);
}
}
}
39 unique and can be used multiple times; iteration should not go back, starting from cur
and only goes after.
class Solution {
public List<List<Integer>> combinationSum(int[] c, int target) {
List<List<Integer>> res = new ArrayList();
if (c.length == 0) return res;
List<Integer> tmp = new ArrayList();
backtrack(res, tmp, c, target, 0);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int[] c, int target, int start) {
if (target < 0) return;
else if (target == 0) {
res.add(new ArrayList(tmp));
return;
}
for (int i = start; i < c.length; ++i) {
tmp.add(c[i]);
backtrack(res, tmp, c, target - c[i], i);
tmp.remove(tmp.size() - 1);
}
}
}
Compare 39 with 40, pattern are the same, only constraints differ.
40: same val number seen as same answer, though different num. When hit, slide to right until different number.
class Solution {
public List<List<Integer>> combinationSum2(int[] c, int target) {
List<List<Integer>> res = new ArrayList();
if (c.length == 0) return res;
Arrays.sort(c);
List<Integer> tmp = new ArrayList();
backtrack(res, tmp, c, target, 0);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int[] c, int target, int start) {
if (target < 0) return;
if (target == 0) {
res.add(new ArrayList(tmp));
return;
}
for (int i = start; i < c.length; ++i) {
if (i > start && c[i] == c[i-1]) continue;
tmp.add(c[i]);
backtrack(res, tmp, c, target - c[i], i+1);
tmp.remove(tmp.size() - 1);
}
}
}
Notice it's if (i > start && c[i] == c[i-1]) continue;
instead of if (i > 0 && c[i] == c[i-1]) continue;
: when i=start
, this number should be examined, not just skipped: evade when curVal == prevVal
and just skipped, though different number
- no sorting; each time start from
0
. The way to evade duplicates isif (tmp.contains(n[i])) continue;
since each time starting from0
. Notice the 4th paramstart
can exist, or not, in the backtrack signature cuz it's going to backtrack fromi+1
next time.
class Solution {
public List<List<Integer>> permute(int[] n) {
List<List<Integer>> res = new ArrayList();
if (n.length == 0) return res;
List<Integer> tmp = new ArrayList();
backtrack(res, tmp, n, 0);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int[] n, int start) {
if (tmp.size() == n.length) {
res.add(new ArrayList(tmp));
return;
}
for (int i = 0; i < n.length; ++i) {
if (tmp.contains(n[i])) continue;
tmp.add(n[i]);
backtrack(res, tmp, n, i+1);
tmp.remove(tmp.size() - 1);
}
}
}
- Pruning:
if (used[i] || i > 0 && nums[i-1] == nums[i] && used[i-1] == false) continue;
; sort and useused[i]
to record if this number was used before; Retreat: undoused[i]
as well
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList();
List<Integer> tmp = new ArrayList();
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtrack(res, tmp, nums, used);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int[] nums, boolean[] used) {
if (tmp.size() == nums.length) {
res.add(new ArrayList(tmp));
return;
}
for (int i = 0; i< nums.length; ++i) {
if (used[i] || i > 0 && nums[i-1] == nums[i] && used[i-1] == false) continue;
used[i] = true;
tmp.add(nums[i]);
backtrack(res, tmp, nums, used);
used[i] = false;
tmp.remove(tmp.size() - 1);
}
}
}
77
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList();
List<Integer> tmp = new ArrayList();
backtrack(res, tmp, n, k, 1);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int n, int k, int start) {
if (k == 0) {
res.add(new ArrayList(tmp));
return;
}
for (int i = start; i <= n-k+1; ++i) {
tmp.add(i);
backtrack(res, tmp, n, k-1, i+1);
tmp.remove(tmp.size() - 1);
}
}
78
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList();
List<Integer> tmp = new ArrayList();
backtrack(res, tmp, nums, 0);
return res;
}
private void backtrack(List<List<Integer>> res, List<Integer> tmp, int[] nums, int start) {
res.add(new ArrayList(tmp));
for (int i = start; i < nums.length; ++i) {
tmp.add(nums[i]);
backtrack(res, tmp, nums, i+1);
tmp.remove(tmp.size() - 1);
}
}
- N-queens
class Solution {
public List<List<String>> solveNQueens(int n) {
char[][] board = new char[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
board[i][j] = '.';
}
}
List<List<String>> res = new ArrayList();
backtrack(board, res, 0);
return res;
}
private void backtrack(char[][] board, List<List<String>> res, int row) {
int n = board.length;
if (row == n) {
res.add(build(board));
return;
}
for (int i = 0; i < n; ++i) {
if (isValid(board, row, i)) {
board[row][i] = 'Q';
backtrack(board, res, row + 1);
board[row][i] = '.';
}
}
}
private boolean isValid(char[][] board, int row, int col) {
for (int i = 0; i < row; ++i) {
if (board[i][col] == 'Q')
return false;
}
for (int i = row -1, j = col - 1; i >=0 && j >= 0; --i, --j) {
if (board[i][j] == 'Q')
return false;
}
for (int i = row -1, j = col + 1; i >=0 && j < board.length; --i, ++j) {
if (board[i][j] == 'Q')
return false;
}
return true;
}
private List<String> build(char[][] board) {
List<String> res = new ArrayList();
for (int i = 0; i < board.length; ++i) {
String s = new String(board[i]);
res.add(s);
}
return res;
}
}
- Sudoku Solver
class Solution {
public void solveSudoku(char[][] board) {
if (board == null || board.length == 0) return;
solve(board);
}
private boolean solve(char[][] board) {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (board[i][j] == '.') {
for (char c = '1'; c <= '9'; ++c) {
if (isValid(board, c, i, j)) {
board[i][j] = c;
if (solve(board))
return true;
board[i][j] = '.';
}
}
return false;
}
}
}
return true;
}
private boolean isValid(char[][] board, char c, int row, int col) {
int cubeRowStart = 3 * (row / 3);
int cubeColStart = 3 * (col / 3);
for (int i = 0; i < 9; ++i) {
if (board[row][i] == c) return false;
if (board[i][col] == c) return false;
if (board[cubeRowStart + i / 3][cubeColStart + i % 3] == c) return false;
}
return true;
}
}