——回溯是DFS中的一种
回溯公式:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
递归:backtrack(路径, 选择列表)
撤销选择
具体解释,可参考知乎文章,写的很好:https://zhuanlan.zhihu.com/p/93530380
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」
决策树如下:
一、全排列
1.1 无重复数字全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
public List<List<Integer>> permute(int[] nums) {//输入:[1,2,3]
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backtrack(res,list,nums);
return res;
}
public void backtrack(List<List<Integer>> res,List<Integer> list,int[] nums){
//满足结束条件时
if(list.size() == nums.length){
res.add(new ArrayList<Integer>(list));//加入到路径
return;
}
for(int i:nums){
//排除不合法选择
if(!list.contains(i)){
//做选择
list.add(i);
//进入下一行决策
backtrack(res,list,nums);
//撤销选择
list.remove(list.size()-1);//递归结束后,将每次递归进去的数撤出
}
}
}
}
1.2 有重复数字全排列
题目来源:LeetCode47https://leetcode-cn.com/problems/permutations-ii/
给定一个可包含重复数字的序列,返回所有不重复的全排列。
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
boolean[] isUsed = new boolean[nums.length];
Arrays.sort(nums);
backtrack(res,list,nums,isUsed);
return res;
}
public void backtrack(List<List<Integer>> res,List<Integer> list,int[] nums,boolean[] isUsed){
//结束条件
if(list.size() == nums.length){
res.add(new ArrayList<Integer>(list));//加入到路径
return;
}
int lastUsed = Integer.MIN_VALUE;
for(int i=0;i<nums.length;i++){
//排除不合法
if(isUsed[i] == false && nums[i] != lastUsed){
isUsed[i] = true;
//做选择
list.add(nums[i]);
//下一决策树
backtrack(res,list,nums,isUsed);
lastUsed = nums[i];
isUsed[i] = false;
//撤销上一个选择
list.remove(list.size()-1);
}
}
}
}
1.3 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
List<Integer> list = new ArrayList<>();
baktrack(list,nums,0);
res.add(new ArrayList<>());//加入空的
return res;
}
public void baktrack(List<Integer> list,int[] nums,int index){
for(int i=index;i<nums.length;i++){
list.add(nums[i]);
res.add(new ArrayList<>(list));
baktrack(list,nums,i+1);
list.remove(list.size()-1);
}
}
}
1.4 字符串全排列
【题目】 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
参考博客:https://blog.csdn.net/Strom72/article/details/80738818
完整代码:
import java.util.*;
public class Solution {
public static ArrayList<String> Permutation(String str) {
ArrayList<String> ans=new ArrayList<>();//所有排列的可能都在这里
if(str!=null||str.length()>0){
help(0,str.toCharArray(),ans);
Collections.sort(ans);
}
return ans;
}
public static void help(int i,char[] cha,ArrayList<String> ans){
if(i==cha.length-1){
String val = String.valueOf(cha);
if(!ans.contains(val)){
ans.add(val);
}
}else{
for(int j=i;j<cha.length;j++){
swap(i,j,cha);//依次选一个数固定住
help(i+1,cha,ans);//让后面的进行全排列
swap(i,j,cha);//恢复原来的模样,回溯关键
}
}
}
public static void swap(int i,int j,char[] cha){
char temp=cha[i];
cha[i]=cha[j];
cha[j]=temp;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String str = sc.next();
System.out.println(Permutation(str));
}
}
二、N皇后问题
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击(包括斜线方向)。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
import java.util.*;
public class Solution {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(solveNQueens(n));
}
public static List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<List<String>>();
int[] queens = new int[n];
Arrays.fill(queens, -1);
Set<Integer> columns = new HashSet<Integer>();//同一列是否有皇后
Set<Integer> diagonals1 = new HashSet<Integer>();//斜线1:左上-右下
Set<Integer> diagonals2 = new HashSet<Integer>();//斜线2:右上-左下
backtrack(res, queens, n, 0, columns, diagonals1, diagonals2);
return res;
}
public static void backtrack(List<List<String>> res, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
if (row == n) {
List<String> board = generateBoard(queens, n);
res.add(board);
} else {
for (int i = 0; i < n; i++) {
if (columns.contains(i)) {
continue;
}
int diagonal1 = row - i;
if (diagonals1.contains(diagonal1)) {
continue;
}
int diagonal2 = row + i;
if (diagonals2.contains(diagonal2)) {
continue;
}
queens[row] = i;
columns.add(i);
diagonals1.add(diagonal1);
diagonals2.add(diagonal2);
backtrack(res, queens, n, row + 1, columns, diagonals1, diagonals2);
queens[row] = -1;
columns.remove(i);
diagonals1.remove(diagonal1);
diagonals2.remove(diagonal2);
}
}
}
public static List<String> generateBoard(int[] queens, int n) {
List<String> board = new ArrayList<String>();
for (int i = 0; i < n; i++) {
char[] row = new char[n];
Arrays.fill(row, '.');
row[queens[i]] = 'Q';
board.add(new String(row));
}
return board;
}
}
三、递增子序列
LeetCode491:https://leetcode-cn.com/problems/increasing-subsequences/
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
class Solution {
public List<List<Integer>> findSubsequences(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backTrace(res,list,nums,-1);
return res;
}
public void backTrace(List<List<Integer>> res,List<Integer> list,int[] nums,int begin){
//判断满足条件
if(list.size()>=2) {
//加入到路径
res.add(new ArrayList<>(list));
}
Set<Integer> set = new HashSet<>();
for(int i = begin+1;i<nums.length;i++){
//排除不合法
if(!set.contains(nums[i])){
set.add(nums[i]);
//做选择:如果出现更大的数
if(begin == -1 || nums[i] >= nums[begin]){
list.add(nums[i]);
//进入下一行决策
backTrace(res,list,nums,i);
//撤销选择
list.remove(list.size()-1);
}
}
}
}
}
四、电话号码的组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
class Solution {
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
int len = digits.length();
if(len==0) {
return res;
}
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");
StringBuffer sb = new StringBuffer();
backtrack(digits,map,res,0,sb);
return res;
}
//回溯
public void backtrack(String digits,Map<Character,String> map,List<String> res,int begin,StringBuffer sb){
//满足条件时,加入到路径
if(begin == digits.length()){
res.add(sb.toString());
return;
}
char c = digits.charAt(begin);
String ss = map.get(c);//要从中选择字符的字符串
for(int i = 0;i < ss.length();i++){
//做选择
sb.append(ss.charAt(i));
//进入下一决策
backtrack(digits,map,res,begin + 1,sb);
//撤销选择
sb.deleteCharAt(begin);
}
}
}
五、和为target的组合
LeetCode39:https://leetcode-cn.com/problems/combination-sum/
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backtrack(res,list,0,candidates,target);
return res;
}
public void backtrack(List<List<Integer>> res,List<Integer> list,int begin,int[] candidates,int target){
//结束条件
if(target==0){
res.add(new ArrayList<Integer>(list));
return;
}
for(int i = begin;i < candidates.length;i++){//i用于遍历candidates
//排除不合法
if(target<0){
break;
}
//做选择
list.add(candidates[i]);
//下一决策
backtrack(res,list,i,candidates,target - candidates[i]);
//撤销上一个选择
list.remove(list.size() - 1);
}
}
}
六、k个数的组合
6.1 数字1-n中k个数的所有组合
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
if(k <= 0 || n < k){
return res;
}
List<Integer> list = new ArrayList<>();
backtrack(1,n,k,res,list);
return res;
}
public void backtrack(int idx,int n,int k,List<List<Integer>> res,List<Integer> list){
if(list.size() == k){
res.add(new ArrayList<>(list));
return;
}
for (int i = idx; i <= n; i++) {
list.add(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
backtrack(i + 1, n, k, res, list);
list.remove(list.size() - 1);
}
}
}
6.2 k个数字组合成n的所有组合
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
if(k<=0 || n<k){
return res;
}
backtrack(k,n,1,res,list);
return res;
}
public void backtrack(int k,int n,int index,List<List<Integer>> res,List<Integer> list){
if(list.size() == k && n == 0){
res.add(new ArrayList<>(list));
return;
}
for(int i = index;i<=9;i++){
list.add(i);
backtrack(k,n - i,i+1,res,list);
list.remove(list.size() - 1);
}
}
}