Backtracking(回溯)属于 DFS。
普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
1. 数字键盘组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
public Map<Character, String> getletterMap() {
Map<Character, String> map = new HashMap<>();
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");
return map;
}
public List<String> letterCombinations(String digits) {
List<String> allCombinations = new LinkedList<>();
if (digits == null || digits.length() == 0) return allCombinations;
Map<Character, String> letterMap = getletterMap();
StringBuilder finishStr = new StringBuilder();
joint(digits, finishStr, letterMap, allCombinations);
return allCombinations;
}
private void joint(String digits, StringBuilder finishStr, Map<Character, String> letterMap, List<String> allCombinations) {
if (finishStr.length() == digits.length()) {
allCombinations.add(finishStr.toString());
return;
}
char curNum = digits.charAt(finishStr.length());
String curLetters = letterMap.get(curNum);
for (char letter : curLetters.toCharArray()) {
finishStr.append(letter);
joint(digits, finishStr, letterMap, allCombinations);
finishStr.deleteCharAt(finishStr.length() - 1);
}
}
2. IP 地址划分
/*
* 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
* 解析:ip地址用三个.分为四段。每一段数字范围是0~255.并且每段不会出现以0开头的,如01,001等
* */
public List<String> restoreIpAddresses(String s) {
List<String> iplist = new LinkedList<>();
StringBuilder frontS = new StringBuilder();
restoreIp(0, s, frontS, iplist);
return iplist;
}
public void restoreIp(int k, String s, StringBuilder frontS, List<String> iplist) {
if (k == 4 || s.length() == 0) {
if (k == 4 && s.length() == 0) {
iplist.add(frontS.toString());
}
return;
}
for (int i = 0; i < s.length() && i <= 2; i++) {
if (s.charAt(0) == '0' && i != 0) break;//注意这个位置
String curS = s.substring(0, i + 1);
int curNum = Integer.valueOf(curS);
if (curNum >= 0 && curNum <= 255) {
if (frontS.length() != 0) {
curS = "." + curS;
}
frontS.append(curS);
restoreIp(k + 1, s.substring(i + 1), frontS, iplist);
frontS = frontS.delete(frontS.length() - curS.length(), frontS.length());
}
}
return;
}
3. 在矩阵中寻找字符串
/*
* 在矩阵中寻找字符串
* */
public boolean exist(char[][] board, String word) {
if (word == null || word.length() == 0) return true;
if (board == null || board.length == 0 || board[0].length == 0) return false;
int m = board.length, n = board[0].length;
int book[][] = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (findWord(word, 0, i, j, board, book)) return true;
}
}
return false;
}
private int direction[][] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
private boolean findWord(String word, int curIndex, int i, int j, char[][] board, int[][] book) {
if (curIndex == word.length()) return true;
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || book[i][j] == 1) return false;
if (word.charAt(curIndex) != board[i][j]) return false;
book[i][j] = 1;
for (int k = 0; k < direction.length; k++) {
int x = i + direction[k][0];
int y = j + direction[k][1];
if (findWord(word, curIndex + 1, x, y, board, book)) return true;
}
book[i][j] = 0;
return false;
}
4. 输出二叉树中所有从根到叶子的路径
/*
* 输出二叉树中所有从根到叶子的路径
* */
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new LinkedList<>();
List<Integer> list = new LinkedList<>();
findAllPath(root, list, paths);
return paths;
}
private void findAllPath(TreeNode root, List<Integer> list, List<String> paths) {
if (root == null) {
return;
}
list.add(root.val);
if (root.right == null && root.left == null) {
paths.add(buildPath(list));
} else {
findAllPath(root.left, list, paths);
findAllPath(root.right, list, paths);
}
list.remove(list.size() - 1);
}
private String buildPath(List<Integer> list) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
str.append(list.get(i));
if (i != list.size() - 1) str.append("->");
}
return str.toString();
}
5. 排列
/*
* 全排列
* 给定一个没有重复数字的序列,返回其所有可能的全排列。
* */
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> alllist = new LinkedList<>();
int n = nums.length;
if (n == 0) return alllist;
boolean visited[] = new boolean[n];
List<Integer> curSeq = new LinkedList<>();
combin(nums, visited, curSeq, alllist);
return alllist;
}
private void combin(int[] nums, boolean[] visited, List<Integer> curSeq, List<List<Integer>> alllist) {
if (nums.length == curSeq.size()) {
alllist.add(new LinkedList<Integer>(curSeq));//!!!!!!重新构造一个 List
return;
}
for (int i = 0; i < nums.length; i++) {
if (!visited[i]) {
visited[i] = true;
curSeq.add(nums[i]);
combin(nums, visited, curSeq, alllist);
curSeq.remove(curSeq.size() - 1);
visited[i] = false;
}
}
}
6. 含有相同元素求排列
/*
*含有相同元素求排列
* 给定一个可包含重复数字的序列,返回所有不重复的全排列。
* */
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> alllist = new LinkedList<>();
int n = nums.length;
if (n == 0) return alllist;
boolean visited[] = new boolean[n];
List<Integer> curSeq = new LinkedList<>();
Arrays.sort(nums);
combin2(nums, visited, curSeq, alllist);
return alllist;
}
private void combin2(int[] nums, boolean[] visited, List<Integer> curSeq, List<List<Integer>> alllist) {
if (nums.length == curSeq.size()) {
alllist.add(new LinkedList<>(curSeq));
return;
}
for (int i = 0; i < nums.length; i++) {
if (visited[i]) continue;
if (i - 1 >= 0 && nums[i] == nums[i - 1] && visited[i - 1]) continue;//去重的三个条件很重要!!!
visited[i] = true;
curSeq.add(nums[i]);
combin2(nums, visited, curSeq, alllist);
curSeq.remove(curSeq.size() - 1);
visited[i] = false;
}
}
7. 组合
/*
* 组合
* 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
* */
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> allCombines = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if (n < k || k <= 0) return allCombines;
search(0, n, k, list, allCombines);
return allCombines;
}
private void search(int cur, int n, int k, List<Integer> list, List<List<Integer>> allCombines) {
if (k == list.size()) {
allCombines.add(new LinkedList<>(list));
return;
}
for (int i = cur + 1; i <= n; i++) {
list.add(i);
search(i, n, k, list, allCombines);
list.remove(list.size() - 1);
}
}
8. 组合求和
/*
* 组合求和
* 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
* candidates 中的数字可以无限制重复被选取。
* */
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> allCombines = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if (target <= 0 || candidates == null || candidates.length == 0) return allCombines;
searchSum(list, allCombines, 0, 0, target, candidates);
return allCombines;
}
private void searchSum(List<Integer> list, List<List<Integer>> allCombines, int curIndex, int curSum, int target, int candidates[]) {
if (curSum == target) {
allCombines.add(new LinkedList<>(list));
return;
}
if (curSum > target) {
return;
}
int tmpSum = curSum;
for (int i = curIndex; i < candidates.length; i++) {
list.add(candidates[i]);
curSum = tmpSum + candidates[i];
searchSum(list, allCombines, i, curSum, target, candidates);
list.remove(list.size() - 1);
}
}
9. 含有相同元素的组合求和
/*
* 含有相同元素的组合求和
* 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
* candidates 中的每个数字在每个组合中只能使用一次。
* */
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> allCombines = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if (target <= 0 || candidates == null || candidates.length == 0) return allCombines;
boolean visited[] = new boolean[candidates.length];
Arrays.sort(candidates);
searchSum2(list, allCombines, visited, 0, 0, target, candidates);
return allCombines;
}
private void searchSum2(List<Integer> list, List<List<Integer>> allCombines, boolean visited[], int curIndex, int curSum, int target, int[] candidates) {
if (curSum == target) {
allCombines.add(new LinkedList<>(list));
return;
}
if (curSum > target) return;
int tmpSum = curSum;
for (int i = curIndex; i < candidates.length; i++) {
if (visited[i]) continue;
if (i != 0 && candidates[i - 1] == candidates[i] && !visited[i - 1])
continue;//前面那个相同的元素已经访问过了,这个元素才可以被访问。避免重复
visited[i] = true;
list.add(candidates[i]);
curSum = tmpSum + candidates[i];
searchSum2(list, allCombines, visited, i + 1, curSum, target, candidates);
list.remove(list.size() - 1);
visited[i] = false;
}
}
10. 1-9 数字的组合求和
/*
* 1-9 数字的组合求和
* 找出所有相加之和为 n 的 k 个数的组合。
* 组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
* */
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> allCombines = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if (k <= 0 || n <= 0) return allCombines;
search2(1, 0, 0, n, k, list, allCombines);
return allCombines;
}
private void search2(int curIndex, int curSum, int count, int n, int k, List<Integer> list, List<List<Integer>> allCombines) {
if (count == k && curSum == n) {
allCombines.add(new LinkedList<>(list));
return;
}
if (curSum < n && count < k) {
int tmpSum = curSum;
for (int i = curIndex; i <= 9; i++) {
list.add(i);
curSum = tmpSum + i;
search2(i + 1, curSum, count + 1, n, k, list, allCombines);
list.remove(list.size() - 1);
}
}
}
11. 子集
/*
* 子集
* 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
* 说明:解集不能包含重复的子集。
* */
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> allSubsets = new LinkedList<>();
List<Integer> list = new LinkedList<>();
searchAllSubsets(0, nums, list, allSubsets);
return allSubsets;
}
private void searchAllSubsets(int curIndex, int[] nums, List<Integer> list, List<List<Integer>> allSubsets) {
allSubsets.add(new LinkedList<>(list));
if (curIndex == nums.length) return;
for (int i = curIndex; i < nums.length; i++) {
list.add(nums[i]);
searchAllSubsets(i + 1, nums, list, allSubsets);
list.remove(list.size() - 1);
}
}
12. 含有相同元素求子集
/*
* 含有相同元素求子集
* 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
* 说明:解集不能包含重复的子集。
* */
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> allSubsets = new LinkedList<>();
List<Integer> list = new LinkedList<>();
Arrays.sort(nums);
boolean visits[] = new boolean[nums.length];
searchAllSubsets2(0, visits, nums, list, allSubsets);
return allSubsets;
}
private void searchAllSubsets2(int curIndex, boolean visited[], int[] nums, List<Integer> list, List<List<Integer>> allSubsets) {
allSubsets.add(new LinkedList<>(list));
if (curIndex == nums.length) return;
for (int i = curIndex; i < nums.length; i++) {
if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue;
if (visited[i]) continue;
visited[i] = true;
list.add(nums[i]);
searchAllSubsets2(i + 1, visited, nums, list, allSubsets);
list.remove(list.size() - 1);
visited[i] = false;
}
}
13. 分割字符串使得每个部分都是回文数
/*
* 分割回文串
* 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
* 返回 s 所有可能的分割方案。
* */
public List<List<String>> partition(String s) {
List<List<String>> allScheme = new LinkedList<>();
List<String> strlist = new LinkedList<>();
if (s == null || s.length() == 0) return allScheme;
segStr(s, allScheme, strlist);
return allScheme;
}
public void segStr(String curStr, List<List<String>> allScheme, List<String> strlist) {
if (curStr.length() == 0) {
allScheme.add(new LinkedList<String>(strlist));
return;
}
for (int i = 0; i < curStr.length(); i++) {
if (isPalindrome(curStr.substring(0, i + 1))) {
strlist.add(curStr.substring(0, i + 1));
segStr(curStr.substring(i + 1), allScheme, strlist);
strlist.remove(strlist.size() - 1);
}
}
}
private boolean isPalindrome(String s) {//判断是否为回文串
int start = 0;
int end = s.length() - 1;
while (start <= end) {
if (s.charAt(start++) != s.charAt(end--)) return false;
}
return true;
}
14. 数独
/*
* 解数独
* 编写一个程序,通过已填充的空格来解决数独问题。
* 一个数独的解法需遵循如下规则:
* 数字 1-9 在每一行只能出现一次。
* 数字 1-9 在每一列只能出现一次。
* 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
* 空白格用 '.' 表示。
* */
boolean rowHas[][];
boolean colHas[][];
boolean blockHas[][];
public void solveSudoku(char[][] board) {
rowHas = new boolean[9][10];//rowHas[i][j]=true表示第i行已经存在数字j
colHas = new boolean[9][10];//colHas[i][j]=true表示第i列已经存在数字j
blockHas = new boolean[9][10];//blockHas[i][j]=true表示第i个块已经存在数字j
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
int curElem = board[i][j] - '0';
if (curElem >= 1 && curElem <= 9) {
rowHas[i][curElem] = true;
colHas[j][curElem] = true;
blockHas[(i / 3) * 3 + j / 3][curElem] = true;
}
}
}
fillNum(board, 0, 0);
}
private boolean fillNum(char[][] board, int row, int col) {
//从左到右,从上到下找到 需要填数字的地方
while (row < 9 && board[row][col] != '.') {
row = col == 8 ? row + 1 : row;
col = col == 8 ? 0 : col + 1;
}
if (row == 9) return true;//(如果都填满了就返回)
//逐一尝试1-9,回溯法确定填这个数字是否合适
for (int num = 1; num <= 9; num++) {
if (rowHas[row][num] || colHas[col][num] || blockHas[(row / 3) * 3 + col / 3][num]) continue;
rowHas[row][num] = true;
colHas[col][num] = true;
blockHas[(row / 3) * 3 + col / 3][num] = true;
board[row][col] = (char) (num + '0');
if (fillNum(board, row, col)) return true;
board[row][col] = '.';
rowHas[row][num] = false;
colHas[col][num] = false;
blockHas[(row / 3) * 3 + col / 3][num] = false;
}
return false;
}
15. N 皇后
/*
* 题目:N 皇后
* 题目描述:在 n*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,求所有的 n 皇后的解。
* 一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。
* */
public List<List<String>> solveNQueens(int n) {
List<List<String>> allScheme = new LinkedList<>();
List<String> list = new LinkedList<>();
if (n == 0) return allScheme;
boolean acrossCorner45[] = new boolean[2 * n ];//标记第i个45度对角上是否有皇后
boolean acrossCorner135[] = new boolean[2 * n ];//标记第i个135度对角上是否有皇后
boolean col[] = new boolean[n];//标记第i列上是否有皇后
placeQueens(allScheme, list, 0, col, acrossCorner45, acrossCorner135, n);
return allScheme;
}
private void placeQueens(List<List<String>> allScheme, List<String> list, int rowIndex, boolean[] col, boolean[] acrossCorner45, boolean[] acrossCorner135, int n) {
if (rowIndex == n) {
allScheme.add(new LinkedList<String>(list));
return;
}
for (int j = 0; j < n; j++) {
if (col[j] || acrossCorner45[j + rowIndex] || acrossCorner135[n - j + rowIndex]) {
continue;
}
String s=create(n,j);
list.add(s);
col[j]=true;
acrossCorner45[j + rowIndex]=true;
acrossCorner135[n - j + rowIndex]=true;
placeQueens(allScheme,list,rowIndex+1,col,acrossCorner45,acrossCorner135,n);
col[j]=false;
acrossCorner45[j + rowIndex]=false;
acrossCorner135[n - j + rowIndex]=false;
list.remove(list.size()-1);
}
}
private String create(int n,int j) {
char str[]=new char[n];
Arrays.fill(str,'.');
str[j]='Q';
return String.valueOf(str);
}