回溯算法定义
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
784.字母大小写全排列
题目描述
给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。
示例:
输入: S = “a1b2”
输出: [“a1b2”, “a1B2”, “A1b2”, “A1B2”]
思路分析:
通过深度优先搜索,如果当前下标为index的字符为字母,则改变字母的大小写,并在递归中形成分支。当递归到下标index==S.length()时,将当前的字符串记录到数组res中。
public List<String> letterCasePermutation(String S) {
dfs(S, 0);
return res;
}
List<String> res = new ArrayList<>();
private void dfs(String S, int index) {
if(index == S.length()) {
res.add(S);
return;
}
dfs(S, index+1);
StringBuilder temp = new StringBuilder();
for(int i=0;i<S.length();i++) {
temp.append(S.charAt(i));
}
char c = temp.charAt(index);
if(Character.isAlphabetic(c)) {
if(Character.isUpperCase(c))
c = Character.toLowerCase(c);
else c = Character.toUpperCase(c);
temp.replace(index, index+1, c+"");
dfs(temp.toString(), index+1);
}
}
78.子集
题目描述
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路分析
递归地遍历数组,每次进行
- 不添加当前元素,将subset直接用于继续递归
- 将当前元素添加到subset中,再将subset添加到res中,然后继续递归
class Solution {
public List<List<Integer>> subsets(int[] nums) {
res.add(new ArrayList<>(0));
dfs(nums, new ArrayList<>(), 0);
return res;
}
List<List<Integer>> res = new ArrayList<>();
private void dfs(int[] nums, List<Integer> subset, int index) {
if(index==nums.length)
return;
dfs(nums, subset, index+1);
List<Integer> temp = new ArrayList<>();
for(int e: subset) {
temp.add(e);
}
temp.add(nums[index]);
res.add(temp);
dfs(nums, temp, index+1);
}
}
90. 子集 II
题目描述
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
思路分析
和78.子集不同的是,输入的元素中可能包含重复元素。如果不考虑元素重复,那么输出的结果为: [ [1], [2], [2], [1,2,2], [2,2], [1,2], [1,2], [] ]。
观察发现,当原数组中元素连续重复时,产生相同子集的原因是:某一个递归过程中,未将之前的重复元素添加到子集中,而将当前的重复元素添加到子集中。故当nums[i]==nums[i-1]时,维护一个count变量和一个subCount变量分别记录当前位置之前原数组重复元素个数、子集中该元素重复个数。若两变量相等,则可以将当前元素添加到子集中。注意:为了让原数组的重复元素紧邻,需要先对原数组进行排序。
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
res.add(new ArrayList<>(0));
dfs(nums, new ArrayList<>(), 0);
return res;
}
List<List<Integer>> res = new ArrayList<>();
private void dfs(int[] nums, List<Integer> subset, int index) {
if(index==nums.length)
return;
//不将当前元素添加到subset中
dfs(nums, subset, index+1);
//将当前元素添加到subset中
if(index>0 && nums[index]==nums[index-1]) {
int subCount=0, i=subset.size()-1;
while(i>=0 && subset.get(i)==nums[index]) {
subCount++;
i--;
}
int count=0, j=index-1;
while(j>=0 && nums[j]==nums[index]) {
count++;
j--;
}
if(subCount!=count)
return;
}
List<Integer> temp = new ArrayList<>();
for(int e: subset)
temp.add(e);
temp.add(nums[index]);
res.add(temp);
dfs(nums, temp, index+1);
}
}
熟悉回溯算法的思路之后可以用同样的方法求解各种题目。
51. N皇后
class Solution {
public List<List<String>> solveNQueens(int n) {
setPos(n, 0, new ArrayList<String>(n));
return res;
}
private List<List<String>> res = new ArrayList<>();
private void setPos(int n, int row, List<String> solution) {
if(row == n) {
addSolution(solution);
return;
}
outter:
for(int k=0;k<n;k++) {
for(int i=0;i<solution.size();i++) {
String temp = solution.get(i);
int j = temp.indexOf('Q');
if(j==k || Math.abs(i-row)==Math.abs(j-k))
continue outter;
}
solution.add(buildRow(n, k));
setPos(n, row+1, solution);
solution.remove(row);
}
}
private void addSolution(List<String> solution){
List<String> temp = new ArrayList<>();
for(String e: solution) {
temp.add(e);
}
res.add(temp);
return;
}
private String buildRow(int n, int positionOfQueen) {
StringBuilder s = new StringBuilder();
for(int count=0;count<positionOfQueen;count++) {
s.append('.');
}
s.append('Q');
while(s.length()<n)
s.append('.');
return s.toString();
}
}
22. 括号生成
class Solution {
public List<String> generateParenthesis(int n) {
addParenthesis(n, new StringBuilder(), 0, 0);
return res;
}
private List<String> res = new ArrayList<>();
private void addParenthesis(int n, StringBuilder temp, int leftCount, int rightCount) {
if(leftCount + rightCount == 2 * n) {
res.add(temp.toString());
}else {
//add left parenthesis
if(leftCount < n) {
temp.append('(');
addParenthesis(n, temp, leftCount+1, rightCount);
}
//add right parenthesis
if(leftCount > rightCount) {
temp.append(')');
addParenthesis(n, temp, leftCount, rightCount+1);
}
}
if(leftCount + rightCount > 0)
temp.deleteCharAt(temp.length()-1);
}
}
1079.活字印刷
class Solution {
public int numTilePossibilities(String tiles) {
for(int i=0;i<tiles.length();i++) {
char c = tiles.charAt(i);
remainingLetters.put(c, remainingLetters.getOrDefault(c, 0)+1);
}
count();
return res;
}
Map<Character, Integer> remainingLetters = new HashMap<>();
int res=0;
private void count() {
for(char c: remainingLetters.keySet()) {
if(remainingLetters.get(c) != 0) {
res++;
remainingLetters.put(c, remainingLetters.get(c)-1);
count();
remainingLetters.put(c, remainingLetters.get(c)+1);
}
}
return;
}
}
216. 组合总和 III
class Solution{
public List<List<Integer>> combinationSum3(int k, int n) {
findCombination(k, n, new ArrayList<Integer>(), 0, 0, 0);
return res;
}
List<List<Integer>> res = new ArrayList<>();
private void findCombination(int k, int n, List<Integer> list, int count, int pre, int sum) {
if(count==k) {
if(sum==n) {
addToResult(list);
}
return;
}
if(sum>=n)
return;
for(int i=pre+1;i<=9;i++) {
list.add(i);
findCombination(k, n, list, count+1, i, sum+i);
list.remove(list.size()-1);
}
return;
}
private void addToResult(List<Integer> list) {
List<Integer> temp = new ArrayList<>(list.size());
for(int e: list)
temp.add(e);
res.add(temp);
return;
}
}