递归、回溯思想类似bfs,通常需要考虑触底条件+回溯后处理(例如题目常有将临时结果记录的最后一项删除)
子集在给定数组中做出所有子集。
public List> subsets(int[] nums) { List
> res = new LinkedList<>(); List
temp = new LinkedList<>(); backTrack(0, nums, res, temp); return res; } private void backTrack(int i, int[] nums, List > res, List
temp) { res.add(new LinkedList<>(temp)); for (int j = i; j < nums.length; j++) { temp.add(nums[j]); backTrack(j + 1, nums, res, temp); temp.remove(temp.size() - 1); } }
该题用递归回溯类似dfs,只不过是在一维数组内进行dfs,每次遇到新数加入结果,递归探到底,到底后回溯需要将结果数列最后一个数删掉。
子集 IIpublic List> subsetsWithDup(int[] nums) { List
> res = new LinkedList<>(); List
temp = new LinkedList<>(); boolean[] visited = new boolean[nums.length]; if (nums.length == 0) { res.add(temp); return res; } Arrays.sort(nums); subSets(nums, 0, res, temp, visited); return res; } private void subSets(int[] nums, int index, List > res, List
temp, boolean[] visited) { res.add(new LinkedList<>(temp)); if (index >= nums.length) { return; } for (int i = index; i < nums.length; i++) { if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } temp.add(nums[i]); visited[i] = true; subSets(nums, i + 1, res, temp, visited); visited[i] = false; temp.remove(temp.size() - 1); } }
相比于之前子集,添加了重复内容,所以需要进行重复性判断。
即使用visited进行是否遍历过的记录,当遇到新值nums[i] == nums[i - 1](前提需要数组排序)说明遇到两个相同值中的第二个,那么有两种情况:
①若i-1数值被用过,说明在重复的数据中,i下标值并不是重复数字中第一次出现,这次使用会出现类似12(i-1)2(i)这种情况,这样不会产生重复数据
②若i-1数值没被用,说明当前i下标值是重复数字中第一次出现,但在数组中并不是重复数字的头部,类似数组122,当第二个2想开始构造,就会与第一个2构造出的数据发生重复
所以由!visited[i - 1]划分出两个情况进行处理。
目的得出给定数组的全部排列组合可能。
public List> permuteUnique(int[] nums) { List
> res = new LinkedList<>(); if (nums.length == 0) { return res; } Arrays.sort(nums); boolean[] visited = new boolean[nums.length]; dfs(nums, visited, nums.length, 0, new LinkedList<>(), res); return res; } private void dfs(int[] nums, boolean[] visited, int len, int size, List
path, List > res) { if (size == len) { res.add(new LinkedList<>(path)); return; } for (int i = 0; i < len; i++) { if (visited[i]) { continue; } if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } path.add(nums[i]); visited[i] = true; dfs(nums, visited, len, size + 1, path, res); visited[i] = false; path.remove(path.size() - 1); } }
相比于子集的区别就是每次以新下标进行遍历时,仍要从头开始遍历,相关注意和子集II相同(相同字符判断等)
组合求和给定数据数组和目标值,需要得出所有可以相加为目标值的组合。
public List> combinationSum(int[] candidates, int target) { List
> res = new LinkedList<>(); if (candidates.length == 0) { return res; } Arrays.sort(candidates); dfs(candidates, 0, target, new LinkedList<>(), res); return res; } private void dfs(int[] candidates, int begin, int target, List
path, List > res) { if (target == 0) { res.add(new LinkedList<>(path)); return; } for (int i = begin; i < candidates.length; i++) { if (target - candidates[i] < 0) { break; } path.add(candidates[i]); dfs(candidates, i, target - candidates[i], path, res); path.remove(path.size() - 1); } }
因为该题数字可以重复使用(即目标值为6,数组2 3,结果可以有{2,2,2}和{3,3})且没有重复元素,所以不用进行相同值判断的处理。
因为每次遍历由当前位置起始,所以遍历过的元素不会再次遍历,不用visited记录。
相比上面题目,该题数字只能用一次
public List> combinationSum2(int[] candidates, int target) { List
> res = new ArrayList<>(); if (candidates.length == 0) { return res; } Arrays.sort(candidates); List
path = new LinkedList<>(); dfs(candidates, 0, target, path, res); return res; } private void dfs(int[] candidates, int begin, int target, List path, List > res) { if (target == 0) { res.add(new ArrayList<>(path)); return; } for (int i = begin; i < candidates.length; i++) { if (target - candidates[i] < 0) { break; } if (i > begin && candidates[i] == candidates[i - 1]) { continue; } path.add(candidates[i]); dfs(candidates, i + 1, target - candidates[i], path, res); path.remove(path.size() - 1); } }
因为数字只能用一次且有重复数据,就要防止重复组合情况出现i > begin && candidates[i] == candidates[i - 1]。
电话号码的字母组合给一串数字,得到其9键可组成的所有字串
public ListletterCombinations(String digits) { List res = new LinkedList<>(); if (digits.length() == 0) { return null; } String[] map = new String[] {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; backTrack(map, res, new StringBuilder(), digits, 0); return res; } private void backTrack(String[] map, List res, StringBuilder stringBuilder, String digits, int index) { if (stringBuilder.length() == digits.length()) { res.add(stringBuilder.toString()); return; } for (char word : map[digits.charAt(index) - '2'].toCharArray()) { stringBuilder.append(word); backTrack(map, res, stringBuilder, digits, index + 1); stringBuilder.deleteCharAt(stringBuilder.length() - 1); } }
该题目和之前求全排列类似,只是取字符换了一种方式,由String[] map = new String[] {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};方便后续依据数字取可能字符。
括号生成给定n组左右括号,生成所有组合并且有效
public ListgenerateParenthesis(int n) { List res = new LinkedList<>(); StringBuilder temp = new StringBuilder(); backTrack(res, temp, n, n); return res; } private void backTrack(List res, StringBuilder temp, int left, int right) { if (left == 0 && right == 0) { res.add(temp.toString()); return; } if (left > right) { return; } if (left > 0) { backTrack(res, temp.append("("), left - 1, right); temp.deleteCharAt(temp.length() - 1); } if (right > 0) { backTrack(res, temp.append(")"), left, right - 1); temp.deleteCharAt(temp.length() - 1); } }
记录左右括号可用数,思路很简单,只需要注意剪枝条件:左括号可用大于右括号(其后无法完成匹配)
单词搜索给定二维数组,查找其内部是否包含目标单词
public boolean exist(char[][] board, String word) {
boolean[][] visited = new boolean[board.length][board[0].length];
int[][] dir = new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (backTrack(board, word, 0, visited, i, j, dir)) {
return true;
}
}
}
return false;
}
private boolean backTrack(char[][] board, String word, int pos, boolean[][] visited, int row, int col, int[][] dir) {
if (row < 0 || col < 0 || row >= board.length || col >= board[0].length || visited[row][col]) {
return false;
}
if (board[row][col] != word.charAt(pos)) {
return false;
}
if (pos == word.length() - 1) {
return true;
}
visited[row][col] = true;
for (int[] ints : dir) {
if (backTrack(board, word, pos + 1, visited, row + ints[0], col + ints[1], dir)) {
return true;
}
}
visited[row][col] = false;
return false;
}
这个题就可以看出经典bfs思路,到新节点,四方向遍历查找。



