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);
}