DFS习题复习
DFS(深度优先搜索)解题思路:recursion的思想。(常见题型:当题目要求穷举所有可能性时,多用DFS),解题思路分两步:
1:考虑总共recursion有几层
2:考虑每一层recursion内有多少个case
1.All Subsets
Given a set of characters represented by a String, return a list containing all subsets of the characters.
Assumptions
- There are no duplicate characters in the original set.
Examples
- Set = "abc", all the subsets are [“”, “a”, “ab”, “abc”, “ac”, “b”, “bc”, “c”]
- Set = "", all the subsets are [""]
- Set = null, all the subsets are []
解题思路:1:recursion层数为:set.length()
2:每层case,只需考虑当前layer对应的character存在或不存在
recursion tree 为(以“abc”为例): {}
"a" level: {a} {}
"b"level: {ab} {a} {b} {}
"c"level: {abc} {ab} {ac} {a} {bc} {b} {c} {}
由recursion tree可以看出,当递归进行到最后一层时,所有得leaf node就为所有得结果:
Java Solution:
1 public List<String> subSets(String set) { 2 List<String>result=new ArrayList<String>(); 3 if(set == null){ 4 return result; 5 } 6 helper(set, new StringBuilder(), 0, result); 7 return result; 8 } 9 private void helper(String set, StringBuilder sb, int layer, List<String>result){ 10 if(layer == set.length()){ 11 result.add(sb.toString()); 12 return; 13 } 14 sb.append(set.charAt(layer)); 15 helper(set, sb, layer+1, result); 16 sb.deleteCharAt(sb.length()-1); 17 helper(set, sb, layer+1, result); 18 }
考虑存在重复元素的情况:
去重方法:
Solution1:如果数组是已经排序好的,我们可以比较容易的去处重复元素,在每一层layer进行完后,只需要跳到下一个和当前值不同的节点即可。
由于整个DFS的时间复杂度为O(2^n),因此可以先将传入数组排序后再进行DFS过程,由于排序时间复杂度仅为o(nlogn)因此并不影响整体的时间复杂度。
Solution2:如果不进行排序,也可以每层maintain一个hashset,存放之前层出现过的charater,跳过所有之前存在过的节点即可。
1 public List<String> subSets(String set) { 2 List<String> result = new ArrayList<String>(); 3 if (set == null) { 4 return result; 5 } 6 if(set.length() == 0) { 7 result.add(""); 8 return result; 9 } 10 char[]array=set.toCharArray(); 11 Arrays.sort(array); 12 set=new String(array); 13 helper(result,set,0,new StringBuilder()); 14 return result; 15 } 16 private void helper(List<String>result,String set, int layer, StringBuilder sb) 17 { 18 if(layer == set.length()) { 19 result.add(sb.toString()); 20 return; 21 } 22 sb.append(set.charAt(layer)); 23 helper(result,set,layer+1,sb); 24 sb.deleteCharAt(sb.length()-1); 25 while(layer < set.length()-1 && set.charAt(layer+1) == set.charAt(layer)) { 26 layer++; 27 } 28 helper(result,set,layer+1,sb); 29 }
2.All Permutations:
Given a string with no duplicate characters, return a list with all permutations of the characters.
Examples
- Set = “abc”, all permutations are [“abc”, “acb”, “bac”, “bca”, “cab”, “cba”]
- Set = "", all permutations are [""]
- Set = null, all permutations are []
解题思路:
1:总共层数,layer=set.length();
2:每层case:同subsets的区别在于,每个character仅是位置互换,而非考虑其存在不存在。因此在每一层recursion中,我们只需穷举所有可能在当前位置出现的character的可能性。具体的实现方法,将当前节点后的每个节点的值和当前节点swap,递归完后再swap回来再交换下一个节点,简称“swap来swap去”
PS:若题目要求需要考虑有重复元素的情况时,去重方法与上题完全相同,既可以从sort的角度去考虑,也可以用Hashset的方法。
Java Solution:
1 public List<String> permutations(String set) { 2 List<String>result=new ArrayList<String>(); 3 if(set==null){ 4 return result; 5 } 6 char[]c=set.toCharArray(); 7 helper(c, result, 0); 8 return result; 9 } 10 private void helper(char[]c, List<String>result,int index){ 11 if(index == c.length){ 12 result.add(new String(c)); 13 return; 14 } 15 for(int i=index; i<c.length; i++){ 16 swap(c, i, index); 17 helper(c, result, index+1); 18 swap(c, i, index); 19 } 20 } 21 private void swap(char[]array, int i, int j){ 22 char temp=array[i]; 23 array[i]=array[j]; 24 array[j]=temp; 25 }
3:All Valid Permutations Of Parentheses
Given N pairs of parentheses “()”, return a list with all the valid permutations.
Assumptions
- N >= 0
Examples
- N = 1, all valid permutations are ["()"]
- N = 3, all valid permutations are ["((()))", "(()())", "(())()", "()(())", "()()()"]
- N = 0, all valid permutations are [""]
解题思路:
1:总共有多少层? 总共层数为括号总数:n*2
2:每层有多少case? 与之前两题不同的是,本题需要考虑左括号和右括号添加priority的问题,既必须得保证,在每次添加右括号时,得保证之前添加的左括号个数大于右括号个数。既如果之前添加过的括号为“()”,在当前层仅能加左括号而不能加右括号,因此每层的case分为两个,既要么加左括号,要么加右括号。
Java Solution:
1 public List<String> validParentheses(int n) { 2 List<String>result=new ArrayList<String>(); 3 helper(n, 0, 0, result, new StringBuilder()); 4 return result; 5 } 6 private void helper(int n, int left, int right, List<String>result, StringBuilder sb){ 7 if(left == n && right == n){ 8 result.add(sb.toString()); 9 return; 10 } 11 if(left < n){ 12 sb.append("("); 13 helper(n, left+1, right, result, sb); 14 sb.deleteCharAt(sb.length()-1); 15 } 16 if(right < left){ 17 sb.append(")"); 18 helper(n, left, right+1, result, sb); 19 sb.deleteCharAt(sb.length()-1); 20 } 21 }
4:Combination of Coins
Given a number of different denominations of coins (e.g., 1 cent, 5 cents, 10 cents, 25 cents), get all the possible ways to pay a target number of cents.
Arguments
- coins - an array of positive integers representing the different denominations of coins, there are no duplicate numbers and the numbers are sorted by descending order, eg. {25, 10, 5, 2, 1}
- target - a non-negative integer representing the target number of cents, eg. 99
Assumptions
- coins is not null and is not empty, all the numbers in coins are positive
- target >= 0
- You have infinite number of coins for each of the denominations, you can pick any number of the coins.
Return
- a list of ways of combinations of coins to sum up to be target.
- each way of combinations is represented by list of integer, the number at each index means the number of coins used for the denomination at corresponding index.
Examples
coins = {2, 1}, target = 4, the return should be
[
[0, 4], (4 cents can be conducted by 0 * 2 cents + 4 * 1 cents)
[1, 2], (4 cents can be conducted by 1 * 2 cents + 2 * 1 cents)
[2, 0] (4 cents can be conducted by 2 * 2 cents + 0 * 1 cents)
]
解题思路:
1:多少层?: coins.length 层(硬币种类数)每一层仅需考虑当前种类的硬币可能存在多少种情况
2:每层多少case?:每层case为每种硬币在当前target的条件下,所有可能出现的情况。比如: 当前剩余硬币为100, 当前硬币面值为3,那当前面值为3的硬币的所有可能性为0-100/3 次,分析时间复杂度时,取worst case,既面值最小硬币的情况,如target=100,最小硬币面值为1时,时间复杂度为o(100^coins.length)
Java Solution:
1 public List<List<Integer>> combinations(int target, int[] coins){ 2 List<List<Integer>>result=new ArrayList<List<Integer>>(); 3 helper(target, coins, result, new ArrayList<Integer>(), 0); 4 return result; 5 } 6 private void helper(int target, int[]coins, List<List<Integer>>result, 7 List<Integer>list, int layer){ 8 if(layer == coins.length-1){ 9 if(target % coins[layer] == 0){ 10 list.add(target/coins[layer]); 11 result.add(new ArrayList(list)); 12 list.remove(list.size()-1); 13 } 14 return; 15 } 16 for(int i=0; i <= target/coins[layer]; i++){ 17 list.add(i); 18 helper(target-(i*coins[layer]), coins, result, list, layer+1); 19 list.remove(list.size()-1); 20 } 21 }
5: N皇后问题
Get all valid ways of putting N Queens on an N * N chessboard so that no two Queens threaten each other.
Assumptions
- N > 0
Return
- A list of ways of putting the N Queens
- Each way is represented by a list of the Queen's y index for x indices of 0 to (N - 1)
Example
N = 4, there are two ways of putting 4 queens:
[1, 3, 0, 2] --> the Queen on the first row is at y index 1, the Queen on the second row is at y index 3, the Queen on the third row is at y index 0 and the Queen on the fourth row is at y index 2.
[2, 0, 3, 1] --> the Queen on the first row is at y index 2, the Queen on the second row is at y index 0, the Queen on the third row is at y index 3 and the Queen on the fourth row is at y index 1.
解题思路与之前题类似,还是分析两个条件:
1:总共有多少层?:显然有N层。
2:每层有多少个case? 每层理论上来说,需要考虑每一个位置,但是并不是所有的节点都valid,由于上下及斜线不能有重复节点,所以在每层递归到下一层前需要先判断该位置是否valid,时间复杂度为n!.
Java Solution:
1 public List<List<Integer>> nqueens(int n) { 2 List<List<Integer>>result=new ArrayList<List<Integer>>(); 3 helper(result, new ArrayList<Integer>(), n, 0); 4 return result; 5 } 6 private void helper(List<List<Integer>>result, List<Integer>list, int n, int layer){ 7 if(n == layer){ 8 result.add(new ArrayList<Integer>(list)); 9 return; 10 } 11 for(int i=0; i<n; i++){ 12 if(isValid(i, list)){ 13 list.add(i); 14 helper(result, list, n, layer+1); 15 list.remove(list.size()-1); 16 } 17 } 18 } 19 private boolean isValid(int i, List<Integer>list){ 20 if(list.isEmpty()){ 21 return true; 22 } 23 int layer=0; 24 for(Integer in: list){ 25 if(i == in || Math.abs(i-in) == Math.abs(list.size()-layer)){ 26 return false; 27 } 28 layer++; 29 } 30 return true; 31 }