1.回溯算法的框架
1.1.解决一个回溯问题,实际上就是一个决策树的遍历过程,只需要思考3个问题
- 1.路径:就是已经做出的选择;
- 2.选择列表:也就是你当前可以做的选择;
- 3.结束条件:也就是到达决策树底层,无法再做出选择的条件。
1.2.代码框架
result = []
def backtrack(路径,选择列表):
if 满足结束条件“
result.add(路径)
return;
for 选择 in 选择列表:
做选择;
backtrack(路径,选择列表);
撤销选择;
2.全排列问题(腾讯50题)
全排列问题
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
2.1.解题思路
- 1.这种数组性质的全排列问题,需要考虑swap交换方法的应用,不应该考虑delete删除方法的应用。
因为delete删除方法,还需要考虑是不是需要new的问题。 - 2.你要理解swap方法的应用,这很重要:
第1个:swap(数组,i,j) 做出选择
第2个:swap(数组,i,j) 撤销选择
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
进行数组的处理,方便后期使用
List<Integer> numsInt = new ArrayList<Integer>();
for (int n: nums) {
numsInt.add(n);
}
1.调用回溯
backtrack(res,numsInt,0);
return res;
}
public void backtrack(List<List<Integer>> res,List<Integer> numsInt,int index) {
0.终止条件
if (first==numsInt.size()-1){
0.5这里需要new一个新的对象,不然即使存进了res,res内的内容海事会动态变化
res.add(new ArrayList<>(numsInt));
return;
}
1.选择列表
for (int i = index; i < numsInt.size(); i++) {
1.1.做出选择
Collections.swap(numsInt,index,i);
1.2.递归
backtrack(res,numsInt,index+1);
1.3撤销选择
Collections.swap(numsInt,index,i);
}
}
}
2+.剑指 Offer 38. 字符串的排列
剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
1.解题思路
- 1.使用swap交换,每次做选择就是交换一次,
第1层递归是依次把下标0及其后的所有元素交换到下标0的位置;
每递进1层就是在上一层的基础上把下标1及其后边的元素交换到下标1的位置;
……
直到递归进入length-1层,就没有了,可以把当前序列存进resList; - 2.雨露均沾:每一层就是让 index及其之后的元素都到index处1次。
class Solution {
public String[] permutation(String s) {
char[] chars = s.toCharArray();
Set<String> set = new HashSet<>();
backTrack(chars,0,set);
String[] res = set.toArray(new String[0]);
return res;
}
public void backTrack(char[] chars,int index,Set<String> set){
0.终止条件
if (index== chars.length-1){
2个知识点
1.set保证排列不重复
2.new Strng保证存入set的元素不会再变化
set.add(new String(chars));
return;
}
1.选择列表
for (int i = index; i < chars.length; i++) {
1.做选择,依次把index及其后的元素交换到index坐标处
swap(chars,index,i);
2.swap交换后的新chars,传入下一层,在新chars基础上,再把index+1,及其之后的元素交换到index+1处
backTrack(chars, index+1, set);
3.撤销选择,准备进入下1轮for循环
swap(chars,index,i);
}
}
public void swap(char[] chars,int i,int j){
Character temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
}
3.数组的全部子集(腾讯50题)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
3.1.解题思路
- 1.此题与全排列问题不同,用不到swap交换方法;
- 2.每次进入backTrack()方法,先添加进上一层传进来的list(Integer),即:res.add(new ArrayList<>(list))(这里一定要new!);
3.在选择列表内做选择,相当于把上一层传进来的单个list子集,末尾再添加选择的元素,组成新的list,并再传入下一层。
4.然后撤销选择。
class Solution {
public List<List<Integer>> subsets(int[] nums) {
if (nums.length==0) return new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
0.进行数组的处理,方便后期使用
List<Integer> numsInt = new ArrayList<Integer>();
for (int n: nums) {
numsInt.add(n);
}
1.调用回溯
backTrack(res,list,numsInt,0);
return res;
}
public void backTrack(List<List<Integer>> res,List<Integer> list,List<Integer> numsInt,int index){
0.每次进来就先添加上次传进来的list
res.add(new ArrayList<>(list));
1.循环列表
for (int i = index; i < numsInt.size(); i++) {
1.1.做选择(list末尾插入选择的元素)
list.add(numsInt.get(i));
1.2.递归
backTrack(res,list,numsInt,i+1); 注意这里是 i+1,而非index+1
1.3.撤销选择(list末尾删除选择的元素)
list.remove(numsInt.get(i));
}
}
}
4.区别:全排列 & 子集
1.在选择列表内做完选择后,调用递归时,下标参数不同!
- 全排列问题传入的是:backtrack(总返回集合,选择路径 ,index+1);
- 子集问题传入的是: backTrack(总返回集合, 选择路径,源数组元素(不用管),i+1);
主要是传入的最后1个参数,因为全排列问题有swap交换操作,所以即使是index+1也不必担心取到重复的元素,但是子集问题,必须是i+1,也就是当前for遍历元素的后1个元素开始;否则会出现重复取某些元素的问题。
2.子集问题 不需要有swap交换操作
- 所以,子集问题在向下一层调用递归传参时,必须传入i+1,确保下一层从本层for循环元素的下一个开始。
5. 组合总和
- 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
- candidates 中的数字可以无限制重复被选取。
- 说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。
1.解题思路
- 1.此问题 数组元素可以重复取,当然,每一个=target的组合是不能重复的。
- 2.先看backTrack(数组(固定), target(固定) ,index(重要!!), comSum(动态加和), pathList(经过路径), resList(总返回));
- 3.backTrack传入参数中只有index需要关注,它是下一层for循环时,在数组中的开始下标
- 4.而且传入下层的index,其实是当前层for循环的 i,为什么?
- 5.因为下一层再从数组中取出元素时,不能取当前层数组下标i之前的元素,为什么?
- 6.因为如果把 i 去掉,直接每层的for循环都从0开始,那就会出现重复的组合,如下图。
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> pathList = new ArrayList<>();
backTrack(candidates,target,0,0,pathList,resList);
return resList;
}
public void backTrack(int[] candi,int target,int index,int comSum,List<Integer> pathList,List<List<Integer>> resList){
0.终止条件 当满足总和 conSum==target时
if (target==comSum){
resList.add(new ArrayList<Integer>(pathList));
System.out.println("+1");
return;
}
0.终止条件:总和 > target 时
if (target<comSum)
return;
1.循环列表
for (int i = index; i < candi.length; i++) {
1.1.做出选择
comSum += candi[i];
pathList.add(candi[i]);
1.2.调用递归
下一层的for循环从本层的下标i开始
backTrack(candi, target,i, comSum, pathList, resList);
1.3.撤销选择
pathList.remove((Integer)candi[i]);
comSum -= candi[i];
}
}
}