0. Gray Code.java Level: Medium Tags: [Backtracking]
TODO:
- backtracking, using set to perform contains()
- BFS: use queue to keep the mutations
题目蛋疼,目前只接受一种结果。
BackTracking + DFS:
Recursive helper里每次flip一个 自己/左边/右边. Flip过后还要恢复原样.遍历所有.
曾用法(未仔细验证):
基本想法就是从一个点开始往一个方向走,每次flip一个bit, 碰壁的时候就回头走。
1. Palindrome Permutation II.java Level: Medium Tags: [Backtracking, Permutation]
TODO: need to review permutation
permutation的综合题:
- validate Input 是不是可以做palindromic permutation. 这个就是(Palindrome Permutation I)
- 顺便存一下permutation string的前半部分和中间的single character(if any)
- DFS 做unique permutation: given input有duplicate characters。
2. Permutation Sequence.java Level: Medium Tags: [Backtracking, Math]
TODO: what about regular DFS/backtracking to compute the kth? dfs(rst, list, candiate list, k)
k是permutation的一个数位。而permutation是有规律的。
也就是说,可以根据k的大小来判断每一个数位的字符(从最大数位开始,因为默认factorio从最大数位开始变化)。
于是先求出n!, 然后 k/n!就可以推算出当下这一个数位的字符。然后分别把factorio 和 k减小。
另外, 用一个boolean[] visited来确保每个数字只出现一次。
这个方法比计算出每个permutation要efficient许多。
3. Letter Combinations of a Phone Number.java Level: Medium Tags: [Backtracking, String]
方法1: Iterative with BFS using queue.
方法2: Recursively adding chars per digit
4. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary
利用 memoization: Map<prefix, List<suffix variations>>
- Realize the input s expands into a tree of possible prefixes.
- We can do top->bottom(add candidate+backtracking) OR bottom->top(find list of candidates from subproblem, and cross-match)
- DFS on string: find a valid word, dfs on the suffix. [NO backtraking in the solution]
- DFS returns List: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- IMPORANT: Memoization:
Map<prefix, List<suffix variations>>, which reduces repeated calculation if the substring has been tried. - Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....', anddict=[a,b,c,d,e,f...]
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
5. Binary Tree Paths.java Level: Easy Tags: [Backtracking, Binary Tree, DFS]
给一个binary tree, 返回所有root-to-leaf path
- Find all paths, bfs/dfs all works. dfs will be simplier to write
- Recursive:分叉. dfs.
- top->bottom: enumerate current node into the list, carry to next level, and backtrack
- top->bottom is trivial to consider: path flows from top->bottom
- We can also take current node.left or node.right to generate list of results from the subproblem
- let dfs return list of string candidates, and we can run pair the list with currenet node, once they come back.
- TODO: can write code to practice
- Iterative, 非递归练习了一下
- 因为要每次切短list, 所以再加了一个Stack 来存level
- 单这道题用dfs更简单, 因为找的就是从头到尾的path, 是dfs的pattern
6. Add and Search Word - Data structure design.java Level: Medium Tags: [Backtracking, Design, Trie]
Trie结构, prefix tree的变形: '.'可以代替任何字符,那么就要iterate这个node所有的children.
节点里面有char, isEnd, HashMap<Character, TrieNode>
Build trie = Insert word:没node就加,有node就移动。
Search word:没有node就报错. 到结尾return true
这题因为'.'可以代替任何possible的字符,没一种都是一个新的path,所以recursive做比较好些。
(iterative就要queue了,麻烦点)
7. Word Search II.java Level: Hard Tags: [Backtracking, DFS, Trie]
给一串words, 还有一个2D character matrix. 找到所有可以形成的words. 条件: 2D matrix 只可以相邻走位.
- 相比之前的implementation, 有一些地方可以优化:
-
- Backtracking时候, 在board[][] 上面mark就可以, 不需要开一个visited[][]
-
- 不需要implement trie的所有方程, 用不到: 这里只需要insert.
- 普通的trie题目会让你search a word, 但是这里是用一个board, 看board的每一个字母能不能走出个Word.
- 也就是: 这里的search是自己手动写, 不是传统的trie search() funcombination
-
- TrieNode里面存在 end的时候存string word, 表示到底. 用完了 word = null, 刚好截断重复查找的问题.
- Build Trie with target words: insert, search, startWith. Sometimes, just:
buildTree(words)and return root. - 依然要对board matrix做DFS。
- no for loop on words. 直接对board DFS:
- 每一层,都会有个up-to-this-point的string. 在Trie里面check它是不是存在。以此判断。
- 若不存在,就不必继续DFS下去了。
- Trie solution time complexity, much better:
- build Trie: n * wordMaxLength
- search: boardWidth * boardHeight * (4^wordMaxLength + wordMaxLength[Trie Search])
- for loop on words: inside, do board DFS based on each word.
- Time cpmplexity: word[].length * boardWidth * boardHeight * (4^wordMaxLength)
- Big improvement: use boolean visited on TrieNode!
- 不要用rst.contains(...), 因为这个是O(n) 在leetcode还是会timeout(lintcode竟然可以pass)!
- 在Trie search() method 里面,凡是visit过的,mark一下。
8. Word Search.java Level: Medium Tags: [Array, Backtracking, DFS]
- 找到开头的字母, 然后recursively DFS 去把word串到底:
- 每到一个字母, 朝四个方向走, 之中一个true就可以.
- Note:每次到一个字母,mark一下'#'. 4个path recurse回来后,mark it back.
- 用一个boolean visited[][]
- Use hash map, key = x@y
9. Word Squares.java Level: Hard Tags: [Backtracking, Trie]
可以开Trie class, 里面用到TrieNode. 开Trie(words) 可以直接initalize with for loop TrieNode 里面可以有一个 List startWith: 记录可以到达这个点的所有string: 有点像树形, ancestor形状的存储.
神操作: 根据square的性质, 如果选中了list of words, 设定 int prefixIndex = list.size(). 取出list里面的所有word[prefixedIndex], 并且加在一起, 就是下一个word candidate的 prefix.
形象一点: list = ["ball", "area"]; prefixIndex = list.size(); ball[prefixIndex] = 'l'; area[prefixIndex] = 'e'; //then candidatePrefix = ball[prefixIndex] + area[prefixIndex] = "le"; 这里就可以用到Trie的那个 findByPrefix function, 在每个点, 都存有所有这个点能产生的candidate. 这时, 试一试所有candidate: dfs
能想到这种倒转的结构来存prefix candidates 在 Trie里面, 这个想法非常值得思考.
10. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- 给一个graph of nodes
- 至关重要: 用
List[] edges; edges[i] = new ArrayList<>();来表示graph: 就是每个node, to all its neighbors - 目标是根据edge 的 direction, 把这个graph 里面的 node sort 一个list
- 如果有cycle, 这个item就不会被放在最后的list 里面.
- 比如: 如果两个课互相是dependency, 就变成了cyclic dependency, 这样不好.
- Kahn algorithem:
- 先build一个graph map: <node, list of nodes >; or
List[] edges; edges[i] = new ArrayList<>(); - count in-degree: inDegree就是每个node上面, 有多少个走进来的edge?
- IMPORTANT: always initialize inDegree map/array with 0
- 那些没有 in-coming-edge的, indegree 其实就 等于 0, 那么他们就应该在final result list里面
- 对这些 indegree == 0 的 nodes BFS, add to queue.
- visit queue 上每个 node: count++, also add this curr node to sorted list
- Check all neighbors/edges of curr node: 如果visit过了, 这个node上的 indegree--
- 如果 indegree == 0, add this node to queue.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- Remember: indegree是周围的node到我这里的次数count
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- 这道题没有要求作出final list, 相对简单, 只要visit每个nodes, 最后确认没有cycle就好了
- 用 visited int[] 来确认是否有cycle. 1 代表 paretNode visited, -1 代表在DFS上一行的标记
- 如果遇到-1, 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false.
- 走完一个node的所有neighbor, 都没有fail, 那么backtracking, set visited[i] = 1
- 真的topo sort会在DFS的底端, 把record放进一个stack, 最后reverse, 就是真的sort order.
- 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>
- List[]的list, 其实是default List
有点绕,但是做过一次就明白一点。
是topological sort的题目。一般都是给有dependency的东西排序。最终都会到一个sink node, 再不会有向后的dependency, 在那个点截止。
我就已这样子的点为map的key, 然后value是以这个node为prerequisite的 list of courses.画个图的话,prerequisite都是指向那个sink node, 然后我们在组成map的时候,都是从sink node 发散回来到dependent nodes.
在DFS里面,我们是反向的, 然后,最先完全visited的那个node, 肯定是最左边的node了,它被mark的seq也是最高的。
而我们的sink node,当它所有的支线都visit完了,seq肯定都已经减到最小了,也就是0,它就是第一个被visit的。
最终结果: 每个有pre-requisit的node都trace上去(自底向上),并且都没有发现cycle.也就说明schedule可以用了。
11. Permutations II.java Level: Medium Tags: [Backtracking]
给一串数组, 找出所有permutation数组. 注意: 给出的nums里面有重复数字, 而permutation的结果需要无重复.
- 排序,
- Mark visited. 通过permutation规律查看是否排出了重复结果
- 并且要检查上一层recursive时有没有略过重复element
- time O(n!)
- 在recursive call里面有for loop, 每次从i=0开始, 试着在当下list上加上nums里面的每一个。
- 从i=0开始,所以会依次recursive每一个nums:
- 因此,例如i=2,肯定比i=3先被访问。也就是:取i=2的那个list permutation肯定先排出来。
- 重复的例子:给出Input[x, y1, y2], 假设y的值是一样的。那么,{x,y1,y2}和{x,y2,y1}是相同结果。
- 综上,y1肯定比y2先被访问,{x,y1,y2}先出。 紧随其后,在另一个recursive循环里,{x,y2...}y2被先访问,跳过了y1。
- 重点:规律在此,如果跳过y1,也就是visited[y1] == false, 而num[y2] == num[y1],那么这就是一个重复的结果,没必要做,越过。
- 结果:那么,我们需要input像{x,y1,y2}这样数值放一起,那么必须排序。
- Idea from: https://www.sigmainfy.com/blog/leetcode-permutations-i-and-ii.html
- 用到 sublist sort
- 用 swap function, 在原数组上调节出来新的permutation
- 注意: 每次拿到新的candidate, 都要把没有permutate的数位sort, 然后再开始swap.
- 这是为了确保, [j]和[j-1]在重复时候, 不用重新记录.
- 给一个visited queue
- 和queue在所有的地方一同populate.
- 然后visited里面存得时visited indexes。 (Not efficient code. check again)
12. N-Queens.java Level: Hard Tags: [Backtracking]
N-Queen 问题, 给数字n, 和 nxn board, 找到所有N-queens的答案.
- 用dfs找所有情况, 每一个iteration, 从找一行里挑合适的点, dfs
- 选中的点加进candidate list 里面, 记得要backtracking.
- 每一个candidate都需要validation, 检查 row, col, 2 diagnal 有没有queen
-
- array 里面不能有 target row#
-
- diagnal. 记得公式:
- row1 - row2 == col1 - col2. Diagnal elelment.fail
- row1 - row2 == - (col1 - col2). Diagnal element. fail
- Draw a 3x3 board to test the 2 scanarios:
- (0,0) and (3,3) are diagnal
- (0,2) and (2,0) are diagnal
13. N-Queens II.java Level: Hard Tags: [Backtracking]
跟 N-Queens 一样, 不是找所有结果, 而是count多少结果.
- 当list.size() == n 的时候,说明找到了一个Solution。
-
- dfs function (List, n)
-
- validate function
14. Path Sum II.java Level: Easy Tags: [Backtracking, DFS, Tree]
给一个inputSum, 然后dfs, 找到所有path, 满足: path sum 跟 inputSum 一样.
- 用remaining sum 来检测是否满足 input path sum 条件
- 满足的时候add to result list
- 两种backtracking:
-
- backtrack 当下node, 加进list, 然后dfs. dfs结束后删掉之前加进去的元素. 非常clean.
-
- backtrack 下一个dfs level增加的value. dfs return 之后, 删掉list里面的末尾元素: 但是删掉的dfs余下的value.
- 第一种backtrack更加好掌握.
- Binary Tree的一个基本题: 找到所有满足条件的path
- 遍历到底,比较sum vs. target
- 注意divide的情况。要把遍历的例子写写
15. Combinations.java Level: Medium Tags: [Backtracking, Combination, DFS]
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
- for loop, recursive (dfs).
- 每个item用一次, 下一个level dfs(index + 1)
- Combination DFS. 画个图想想. 每次从1~n里面pick一个数字i
- 因为下一层不能重新回去 [0~i]选,所以下一层recursive要从i+1开始选。
16. Restore IP Addresses.java Level: Medium Tags: [Backtracking, DFS, String]
给一串数字, 检查是否是valid IP, 如果合理, 给出所有valid 的IP组合方式.
- 递归的终点:list.zie() == 3, 解决最后一段
- 递归在一个index上面, pass s.toCharArray()
- validate string要注意leading '0'
- 注意: 递归的时候可以用一个start/level/index来跑路
- 但是尽量不要去改变Input source, 会变得非常confusing.
- note: code有点messy, 因为要考虑IP的valid情况
- 那个'remainValid', 其实是一个对于remain substring的判断优化, 不成立的就不dfs了
17. Expression Add Operators.java Level: Hard Tags: [Backtracking, DFS, Divide and Conquer, String]
给一个数字String, 数字来自
0-9, 给3个操作符+,-,*, 看如何拼凑, 可以做出结果target.output 所有 expression
- 跟string相关, 写起来可能稍微繁琐一点
- 数字有 dfs([1,2,3...]) 组合方法
- operator有[
+,-,*] 3种组合方法 - 注意1: 乘号要特殊处理, pass along 连乘的数字, 计算下一步乘积的时候, 要 sum - preProduct + product
- 注意2: '01' 这种数字要skip
- 注意3: 第一个选中数字不需要加操作符, 直接加进去
- Time: O(4^n), Space: O(4^n)
- T(n) = 3 * T(n-1) + 3 * T(n-2) + 3 * T(n-3) + ... + 3 *T(1);
- T(n-1) = 3 * T(n-2) + 3 * T(n-3) + ... 3 * T(1);
- Thus T(n) = 4T(n-1) = 4^2 * T(n - 1) = .... O(4^n)
- 逻辑一样, 代码更短, 只不过不做list, 直接pass
buffer + "+" + curr - 因为每次都创建新string, 所以速度稍微慢一点. Time complexity 一样
18. Generate Parentheses.java Level: Medium Tags: [Backtracking, DFS, Sequence DFS, String]
- start with empty string, need to go top->bottom
- 取或者不取
(,) - rule: open parentheses >= close parentheses
- Note: 在DFS时 pass a reference (StringBuffer) and maintain, instead of passing object (String) and re-create every time
- time: O(2^n), pick/not pick, the decision repat for all nodes at every level
- T(n) = 2 * T(n - 1) + O(1)
- figure out n=1, n=2 => build n=3, and n=4
- dfs(n-1) return a list of candidates
- add a pair of
()to the candidates: either in front, at end, or contain the candidates
19. Flip Game II.java Level: Medium Tags: [Backtracking, DFS, DP]
String 只包含 + , - 两个符号. 两个人轮流把consecutive连续的
++, 翻转成--.如果其中一个人再无法翻转了, 另一个人就赢. 求: 给出string, 先手是否能赢.
- curr player 每走一步, 就生成一种新的局面, dfs on this
- 等到dfs结束, 不论成功与否, 都要backtracking
- curr level: 把"++" 改成 "--"; backtrack的时候, 改回 '--'
- 换成boolean[] 比 string/stringBuilder要快很多, 因为不需要重新生成string.
- ++ 可以走 (n - 1)个位置:
- T(N) = (N - 2) * T(N - 2) = (N - 4) * (N - 2) * T(N - 4) ... = O(N!)
- 做一个String s的 replica: string or stringBuilder
- 每次dfs后, 然后更替里面的字符 "+" => "-"
- 目的只是Mark已经用过的index
- 真正的dfs 还是在 original input string s 身上展开
- 每次都重新生成substring, 并不是很efficient
- 保证p1能胜利,就必须保持所有p2的move都不能赢
- 或者说, 在知道棋的所有情况时, 只要p2有一种路子会输, p1就一定能走对路能赢.
- 同时,p1只要在可走的Move里面,有一个move可以赢就足够了。
- p1: player1, p2: player2
- 需要Game Theory的功底, Nim game. https://www.jiuzhang.com/qa/941/
- http://www.1point3acres.com/bbs/thread-137953-1-1.html
- TODO: https://leetcode.com/problems/flip-game-ii/discuss/73954/Theory-matters-from-Backtracking(128ms)-to-DP-(0ms)
20. Palindrome Partitioning.java Level: Medium Tags: [Backtracking, DFS]
给个string s, partition(分段)后, 要确保每个partition都是palindrome.
求所有partition palindrome组合.
list<list<string>>- 可以top->bottom: 遍历str, validate substring(start, i); if valid, add as candidate, and dfs; backtrack by remove candidate.
- 也可以bottom->up: 遍历str, validate substring(start, i); if valid, dfs(remaining str), return list of suffix; cross match with curr candidate.
- 在遍历str的时候,考虑从每个curr spot 到 str 结尾,是能有多少种palindorme?
- 那就从curr spot当个字符开始算,开始backtracking.
- 如果所选不是palindrome, 那move on.
- 若所选的确是palindrome, 加到path里面,DFS去下个level,等遍历到了结尾,这就产生了一种分割成palindrome的串。
- 每次DFS结尾,要把这一层加的所选palindrome删掉,backtracking嘛
- 可以再每一个dfs level 算 isPalindrome(S), 但是可以先把 boolean[][] isPalin 算出来, 每次O(1) 来用
- 注意: isPalin[i][j] 是 inclusive的, 所以用的时候要认准坐标
- Calculate isPalin[i][j]: pick mid point [0 ~ n]
- expand and validate palindrome at these indexes:
[mid, mid+1]or[mid-1][mid+1]
- Overall Space O(n^2): 存 isPlain[][]
- Time O(2^n), 每一层都在做 pick/not pick index i 的选择, 所以worst case 2^n.
- 因为我们的isPalin[][]优化了palindrome的判断O(1), 所以overall Time: O(2^n)
21. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
time: O(2^n) space: O(2^n)
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n), 3ms
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- Regular BFS, 注意考虑如果让one level to generate next level
-
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- should be same O(2^n), but actual run time 7ms, slower
22. Subsets II.java Level: Medium Tags: [Array, BFS, Backtracking, DFS]
time: O(2^n) sapce: O(2^n)
给一串integers(may have duplicates), 找到所有可能的subset. result里面不能有重复.
- DFS, 找准需要pass along的几个数据结构. 先
sort input, 然后DFS - Using for loop approach: 每个dfs call是一种可能性,直接add into result.
- 为了除去duplicated result, skip used item at current level:
if (i > depth && nums[i] == nums[i - 1]) continue; - sort O(nlogn), subset: O(2^n)
- space O(2^n), save results
- Regular BFS, 注意考虑如果让one level to generate next level
- skip duplicate:
if (i > endIndex && nums[i] == nums[i - 1]) continue; -
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- srot O(nlogn), subset: O(2^n)
- should be same O(2^n). slower than dfs
- 在DFS种skip duplicate candidates, 基于sorted array的技巧:
- 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],
- 说明x=nums[i-1]已经在curr level 用过,不需要再用一次: [a,x1,x2],x1==x2
- i == index -> [a,x1]
- i == index + 1 -> [a,x2]. 我们要skip这一种
- 如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。
- 不能去用result.contains(), 这本身非常costly O(nlogn)
- 几遍是用 list.toString() 其实也是O(n) iteration, 其实也是增加了check的时间, 不建议
23. Combination Sum.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
time: O(n!) space: O(n!)
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
- 考虑input: 没有duplicate, 不需要sort
- 考虑重复使用的规则: 可以重复使用, 那么for loop里面dfs的时候, 使用curr index i
- the result is trivial, save success list into result.
- at each level dfs, we have the index as starting point:
- if we are at
index=0, we can have n child dfs() options via for loop; - if at
index=1, we will have (n-1) dfs options via for loop. - Consider it as the
pick/not-pickproblem, where the difference is you can pickxtimes at each index rather than only 2 times. - Overall, we will multiply the # of possibilities: n * (n - 1) * (n - 2) ... * 1 = n! =>
O(n!)
- 在每个index上面都要面临:
pick/not pick的选择, 用for loop over index + backtracking 实现 picks. - 每次pick以后, 就生成一条新的routine, from this index
- 下一个level的dfs从这个index开始, 对后面(或者当下/if allow index reuse) 进行同样的 pick/not pick 的选择
- 注意1: 每个level dfs 里面, for loop 里会有 end condition: 就不必要dfs下去了.
- 注意2: Backtracking在success case && dfs case 后都要做, 因为backtrack 是为了之前上一层dfs.
24. Combination Sum II.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (can have duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 只可以用一次.
- when the input has duplicates, and want to skip redundant items?
-
- sort. 2. in for loop, skip same neighbor.
- 考虑input: 有duplicate, 必须sort
- 考虑重复使用的规则: 不可以重复使用
-
- for loop里面dfs的时候, 使用curr index + 1
-
- for loop里面, 同一个level, 同一个数字, 不能重复使用:
(i > index && candidates[i] == candidates[i - 1]) continue
- for loop里面, 同一个level, 同一个数字, 不能重复使用:
- 因为在同一个level里面重复的数字在下一个dfs level里面是会被考虑到的, 这里必须skip (这个就记住吧)
- the result is trivial, save success list into result.
- Which one?
- Time: every level has 1 less element to choose, worst case is: cannot find any solution over all combinations: O(m!)
- Time: Same as
subsetII, pick/not=pick an item as we go, no reuse of item. Worst case: all unique items in the set. O(2^n)
25. Combination Sum III.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一个integer k, 和一个target n.
从positive数字[1 ~ 9], 找到所有unique的 组合(combination) int[], size = k, 要求每个combination的和 = n.
(隐藏条件, 需要clarify): 同一个candidate integer [1 ~ 9], 只可以用一次.
- 跟Combination Sum I, II 没什么太大区别, 只不过, 一定要用k个数字, 也就是一个for loop里面的特别条件
- 考虑input: 没有重复数字 [1 ~ 9]
- 考虑candidate重复利用: 不可以重复利用, next level dfs 时候, curr index + 1
- the result is trivial, save success list into result.
- Which one?
- worst case: tried all numbers and cannot find: O(m!), m = 9, all possible integers in [1~9]
- C(n,k), n choose k problem :
n! / (k! * (n-k)!)=> ends up beingO(min(n^k, n^(n-k)))
26. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
- 本质: 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先
- 把 string array 变成topological sort的 graph:
map<char, list<char>> - 也可以
List[26] edges(Course Schedule problem) - Build edges: find char diff between two row, and store the order indication into graph
- 注意: indegree 永远是反向的 (跟 node to neighbors 相反的方式建立)
- topological sort 本身很好写, 但是要在题目中先了解到字母排序的本质
- 其实上面这个排序的本质很好想, 但是把它具体化成构建graph的代码, 会稍微有点难想到
- 算indegree, 然后用 BFS 来找到那些 inDegree == 0的 node
- 最先inDegree == 0的node, 就排在字母表前面.
- 下面的解法, 用了Graph: map<Character, List>, 而不是 List[26], 其实更加试用超过26个字母的dictionary.
- 如果
inDegree.size() != result.length(), there is nodes that did not make it into result. - ex: cycle nodes from input, where inDegree of a one node would never reduce to 0, and will not be added to result
- In this case, it will be treated as invalid input, and return ""
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
27. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidatesusing queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>to store candidates, searching from end-> start
28. Permutations.java Level: Medium Tags: [Backtracking, DFS, Permutation]
- Given a remaining list: 取, 或者不取
- always iterate over full
nums[], use list.contains() to check if item has been added. - Improvement: maintain list (add/remove elements) instead of 'list.contains'
- time O(n!): visit all possible outcome
- T(n) = n * T(n-1) + O(1)
- 插入法:
-
- 一个一个element加进去
-
- 每一次把rst里面的每个list拿出来, 创建成新list, 然后选位置加上new element
-
- 加新元素的时候, 要在list的每个位置insert, 最终也要在原始的list末尾加上new element
- 还是O(n!), 因为rst insert O(n!)个permutations
- 但是比dfs要快, 因该是因为 # of checks 少: 不需要check list.size(), 不需要maintain remaining list.
- 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍
- Time O(n!)
- A bit slower, possibly because of the polling and saving the entire list every time
29. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
这里的区别是, '*' 需要有一个preceding element, 那么:
- repeat 0 times
- repeat 1 times: need s[i-1] match with prior char p[i-2]
30. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
31. Robot Room Cleaner.java Level: Hard Tags: [Backtracking, DFS]
- Different from regular dfs to visit all, the robot move() function need to be called, backtrack needs to move() manually and backtracking path shold not be blocked by visited positions
- IMPORTANT: Mark on the way in using set, but
backtrack directly without re-check against set - Mark coordinate 'x@y'
- Backtrack: turn 2 times to revert, move 1 step, and turn 2 times to revert back.
- Direction has to be up, right, down, left.
int [] dx = {-1, 0, 1, 0};,int[] dy = {0, 1, 0, -1};