模板

result = {}
void backtrack(選擇列表, 路徑) {
if (滿足結束條件) {
result.add(路徑)
return
} for 選擇 in 選擇列表 {
做選擇
backtrack(選擇列表,路徑)
撤銷選擇
}
}

核心就是從選擇列表裡做一個選擇,然後一直遞迴往下搜尋答案,如果遇到路徑不通,就返回來撤銷這次選擇。

推薦閱讀回溯思想團滅排列、組合、子集問題

78. 子集

給你一個整數陣列 nums ,陣列中的元素 互不相同 。返回該陣列所有可能的子集(冪集)。

解集 不能 包含重複的子集。你可以按 任意順序 返回解集。

class Solution {
public List<list<integer>> subsets(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
dfs(nums, track, 0);
return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs (int[] nums, LinkedList<integer> track, int start) {
res.add(new LinkedList<>(track)); //通過start來控制當前可以選擇的列表
for (int i = start; i < nums.length; i++) {
//選擇
track.offerLast(nums[i]);
dfs(nums, track, i + 1);
//撤銷選擇
track.pollLast();
}
}
} // {1, 2, 3}對應遞迴樹:
// []
// 1 2 3
// 12 13 23
// 123

90. 子集 II

給你一個整數陣列 nums ,其中可能包含重複元素,請你返回該陣列所有可能的子集(冪集)。

解集 不能 包含重複的子集。返回的解集中,子集可以按 任意順序 排列。

class Solution {
public List<list<integer>> subsetsWithDup(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
//先排序
Arrays.sort(nums);
dfs(nums, track, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] nums, LinkedList<integer> track, int start) {
res.add(new LinkedList<>(track)); for (int i = start; i < nums.length; i++) {
//剪枝(跳過重複的)
if ((i != start) && (nums[i] == nums[i - 1])) {
continue;
} track.offerLast(nums[i]);
dfs(nums, track, i + 1);
track.pollLast();
}
}
} // {1, 2, 2, 3}對應遞迴樹:
// []
// 1 2 2(剪) 3
// 12 12(剪) 13 22 23 23
// 123 123 223

46. 全排列

給定一個 沒有重複 數字的序列,返回其所有可能的全排列。

class Solution {
public List<list<integer>> permute(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
boolean[] visited = new boolean[nums.length]; dfs (nums, track, visited); return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
// base case
if (track.size() == nums.length) {
res.add(new LinkedList<integer>(track));
return;
} for (int i = 0; i < nums.length; i++) {
// track中已經包含的就跳過
if (visited[i]) {
continue;
} // 選擇
track.offerLast(nums[i]);
visited[i] = true; dfs(nums, track, visited); // 撤銷選擇
track.pollLast();
visited[i] = false;
}
}
}

47. 全排列 II

給定一個可包含重複數字的序列 nums按任意順序 返回所有不重複的全排列。

class Solution {
public List<list<integer>> permuteUnique(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
boolean[] visited = new boolean[nums.length];
Arrays.sort(nums); dfs (nums, track, visited); return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
// base case
if (track.size() == nums.length) {
res.add(new LinkedList<integer>(track));
return;
} for (int i = 0; i < nums.length; i++) {
// track中已經包含的就跳過
if (visited[i]) {
continue;
} // 剪枝
if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
} // 選擇
track.offerLast(nums[i]);
visited[i] = true; dfs(nums, track, visited); // 撤銷選擇
track.pollLast();
visited[i] = false;
}
}
}

推薦閱讀回溯搜尋 + 剪枝

39. 組合總和

給定一個無重複元素的陣列 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和為 target 的組合。

candidates 中的數字可以無限制重複被選取。

說明:

  • 所有數字(包括 target)都是正整數。
  • 解集不能包含重複的組合。
class Solution {
public List<list<integer>> combinationSum(int[] candidates, int target) {
LinkedList<integer> track = new LinkedList<>();
dfs(candidates, track, target, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
//base case
if (target < 0) {
return;
}
//base case
if (target == 0) {
res.add(new LinkedList<>(track));
return;
} //通過start改變可選列表
for (int i = start; i < candidates.length; i++) {
track.offerLast(candidates[i]);
dfs(candidates, track, target - candidates[i], i);
track.pollLast();
}
}
}

推薦閱讀:回溯演算法 + 剪枝(回溯經典例題詳解)

class Solution {
public List<list<integer>> combinationSum(int[] candidates, int target) {
LinkedList<integer> track = new LinkedList<>();
Arrays.sort(candidates); //排序後可實現進一步剪枝
dfs(candidates, track, target, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
//不會再出現target小於0的情況
//base case
if (target == 0) {
res.add(new LinkedList<>(track));
return;
} //通過start改變可選列表
for (int i = start; i < candidates.length; i++) {
//由於此時candidates是有序的,candidates[i] 後面的元素都大於target,直接忽略
if (target - candidates[i] < 0) {
break;
} track.offerLast(candidates[i]);
dfs(candidates, track, target - candidates[i], i);
track.pollLast();
}
}
}

17. 電話號碼的字母組合

給定一個僅包含數字 2-9 的字串,返回所有它能表示的字母組合。答案可以按 任意順序 返回。

給出數字到字母的對映如下(與電話按鍵相同)。注意 1 不對應任何字母。

class Solution {
public List<string> letterCombinations(String digits) {
if (digits == null || digits.length() <= 0) {
return new LinkedList<string>();
} StringBuilder track = new StringBuilder();
dfs(digits, 0, track);
return res;
} private Map<character, string[]=""> map = new HashMap<>() {
{
put('2', new String[] {"a", "b", "c"});
put('3', new String[] {"d", "e", "f"});
put('4', new String[] {"g", "h", "i"});
put('5', new String[] {"j", "k", "l"});
put('6', new String[] {"m", "n", "o"});
put('7', new String[] {"p", "q", "r", "s"});
put('8', new String[] {"t", "u", "v"});
put('9', new String[] {"w", "x", "y", "z"});
}
}; private List<string> res = new LinkedList<>();
private void dfs(String digits, int index, StringBuilder track) {
if (index == digits.length()) {
res.add(track.toString());
return;
} String[] alphabets = map.get(digits.charAt(index));
for (int i = 0; i < alphabets.length; i++) {
// 選擇
track.append(alphabets[i]); dfs(digits, index + 1, track); // 撤銷選擇
track.deleteCharAt(track.length() - 1);
}
} }

131. 分割回文串

給你一個字串 s,請你將 s 分割成一些子串,使每個子串都是 迴文串 。返回 s 所有可能的分割方案。

迴文串 是正著讀和反著讀都一樣的字串。

class Solution {
public List<list<string>> partition(String s) { // 先使用動態規劃獲得任意兩個區間的字串是否為迴文字串
boolean[][] isPalindrome = getPalindrome(s); LinkedList<string> track = new LinkedList<>();
dfs(s, 0, track, isPalindrome); return res; } private List<list<string>> res = new LinkedList<>();
private boolean[][] getPalindrome(String s) {
int len = s.length(); // 區間i,j的字串是否為迴文字串(左右都為閉區間)
boolean[][] dp = new boolean[len][len]; for (int j = 0; j < len; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
}
}
} return dp;
} private void dfs(String s, int start, LinkedList<string> track, boolean[][] isPalindrome) {
if (start == s.length()) {
res.add(new LinkedList<>(track));
return;
} for (int i = start; i < s.length(); i++) {
// 如果區間[start, i]的字串不為迴文字串就可以剪枝
if (!isPalindrome[start][i]) {
continue;
} // 選擇
track.offerLast(s.substring(start, i + 1)); dfs(s, i + 1, track, isPalindrome); // 撤銷選擇
track.pollLast();
}
}
}

93. 復原 IP 地址

給定一個只包含數字的字串,用以表示一個 IP 地址,返回所有可能從 s 獲得的 有效 IP 地址 。你可以按任何順序返回答案。

有效 IP 地址 正好由四個整數(每個整數位於 0 到 255 之間組成,且不能含有前導 0),整數之間用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "[email protected]" 是 無效 IP 地址。

class Solution {
public List<string> restoreIpAddresses(String s) {
int len = s.length();
if (len < 4 || len > 12) return new LinkedList<>(); LinkedList<string> track = new LinkedList<>();
dfs(s, 0 ,track); return res;
} private List<string> res = new LinkedList<>();
private void dfs(String s, int start, LinkedList<string> track) {
// 只有track已經有4段,且沒有剩餘字元時符合條件
if (track.size() == 4) {
if (start == s.length()) {
res.add(toStr(track));
} return;
} for (int i = start; i < start + 3 && i < s.length(); i++) {
String buf = s.substring(start, i + 1); if (!isValid(buf)) {
continue;
} track.offerLast(buf); dfs(s, i + 1, track); track.pollLast();
}
} private String toStr(LinkedList<string> list) {
return String.join(".", list);
} private boolean isValid(String str) {
int len = str.length();
int buf = Integer.parseInt(str); // 排除 09 等
if (len == 2 && buf < 10) {
return false;
// 排除 099、256 等
} else if (len == 3 && (buf < 100 || buf > 255)) {
return false;
} return true;
}
}