回溯算法通用框架
回溯法框架
result = []
backTry(选择列表,路径){
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
递归调用
撤销选择
}
路径:已经做的选择
选择列表:当前可选列表
总结:
如果数组中有重复数字,要求输出的结果不能有重复集合,一般解决办法:
对数组排序,使得相同的数字挨着;
使用visited数组标记数字是否被访问过。如果nums[i]==nums[i-1]且visited[i-1]=false则nums[i]不再访问,以免重复。
46. 全排列
class Solution {
List<List<Integer>> all; // 用于存储全部符合条件的结果
boolean[] visited;
public List<List<Integer>> permute(int[] nums) {
all = new ArrayList<List<Integer>>();
visited = new boolean[nums.length];
List<Integer> list = new ArrayList<Integer>(); // 用于存储当前路径
backTry(list, nums);
return all;
}
public void backTry(List<Integer> list, int[] nums){
if(list.size() == nums.length){ // 满足结束条件:选了nums中所有的数字
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i]==false){ // 只有没有访问过,才做选择
// 做选择
list.add(nums[i]);
visited[i] = true;
// 递归调用
backTry(list, nums);
// 撤销选择
list.remove(list.size()-1);
visited[i] = false;
}
}
}
}
47. 全排列 II
class Solution {
// nums中有重复数字,要求输出结果不能有重复
List<List<Integer>> all; // 用于存储全部符合条件的结果
boolean[] visited;
public List<List<Integer>> permuteUnique(int[] nums) {
all = new ArrayList<List<Integer>>();
visited = new boolean[nums.length];
Arrays.sort(nums);
List<Integer> list = new ArrayList<Integer>(); // 用于存储当前路径
backTry(list, nums);
return all;
}
public void backTry(List<Integer> list, int[] nums){
if(list.size() == nums.length){ // 满足结束条件:选了nums中所有的数字
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i] || i>0 && nums[i]==nums[i-1] && !visited[i-1]){continue;}
if(visited[i]==false){ // 只有没有访问过,才做选择
// 做选择
list.add(nums[i]);
visited[i] = true;
// 递归调用
backTry(list, nums);
// 撤销选择
list.remove(list.size()-1);
visited[i] = false;
}
}
}
}
78. 子集
数组nums中不包含重复元素,要求输出结果是没有重复子集的 nums = [1,2,3]
不能有重复子集:那么就是当前数字选择完后,选择后面的数字,这样就会避免出现[1,2] [2,1]这样的重复情况
class Solution {
// 幂集:从空到数组长度所有的子集
List<List<Integer>> all; // 存放所有的路径
boolean[] visited;
public List<List<Integer>> subsets(int[] nums) {
all = new ArrayList<List<Integer>>();
visited = new boolean[nums.length];
List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
for(int i = 0;i < nums.length; i++){
backTry(list, nums, i); // 从空路径、长度为0开始选择
}
return all;
}
public void backTry(List<Integer> list, int[] nums, int len){
if(list.size() == len){ // 结束条件:如果当前选择的数字个数等于幂集的长度
all.add(new ArrayList<Integer>(list));
return;
}
for(int i = 0;i < nums.length; i++){
if(visited[i] == false){ // 只有当没有被访问过才选择
// 做选择
list.add(nums[i]);
visited[i] = true;
// 递归调用
backTry(list, nums, len);
// 撤回选择
list.remove(list.size()-1);
visited[i] = false;
}
}
}
}
怎么在回溯的过程中对子集去重呢??上一版代码可以看出,2选择之后又去选择1,但其实是每个数字都选择他后面的数字就不会出现有[1,2] 和 [2,1]这样的情形了。
class Solution {
// 幂集:从空到数组长度所有的子集
List<List<Integer>> all; // 存放所有的路径
public List<List<Integer>> subsets(int[] nums) {
all = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
for(int i = 0;i <= nums.length; i++){
backTry(list, nums, 0, i); // 从空路径、位置0,长度为i开始选择
}
return all;
}
public void backTry(List<Integer> list, int[] nums, int idx, int len){
if(list.size() == len){ // 结束条件:如果当前选择的数字个数等于幂集的长度
all.add(new ArrayList<Integer>(list));
return;
}
for(int i = idx;i < nums.length; i++){
// 做选择
list.add(nums[i]);
// 递归调用,为避免重复,每次选择该数字的下一个数字
backTry(list, nums, i+1, len);
// 撤回选择
list.remove(list.size()-1);
}
}
}
90. 子集 II
class Solution {
// 上一题的数组中不包含重复元素,本题nums中有重复元素,但要求不变
// 怎么避免[2] [2] [1,2] [1,2] 这样的子集出现?可以发现:如果第一个2 被访问过,第二个2与前一个2相同,且前一个2北方问过,那么第二个2就不能被访问,所以去重,我们可以借助visited数组
List<List<Integer>> all; // 存放所有的幂集
boolean[] visited;
public List<List<Integer>> subsetsWithDup(int[] nums) {
all = new ArrayList<List<Integer>>();
visited = new boolean[nums.length];
List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
Arrays.sort(nums);
for(int i=0;i<=nums.length;i++){ // 幂集的长度从0到数组长度
backTry(list, nums, i, 0); // 仍旧从空选择、幂集长度为i,位置0开始选择
}
return all;
}
public void backTry(List<Integer> list, int[] nums, int len, int idx){
if(list.size() == len){ // 结束条件:仍旧是list中数字的个数等于幂集的长度
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=idx;i<nums.length;i++){ // 遍历所有的选择
// 如果当前数组访问过,或者当前数字等于前一个数字,且前一个数字没有被访问过(因为是从前往后访问的的,入股哦前一个数字是false,说明已经访问过并且回溯了的,那么这个数字就不再访问了)
if(visited[i] || i>0 && nums[i] == nums[i-1] && !visited[i-1]){continue;}
// 做选择
list.add(nums[i]);
visited[i] = true;
// 递归调用
backTry(list, nums, len, i+1);
// 撤回选择
list.remove(list.size()-1);
visited[i] = false;
}
}
}
77. 组合
class Solution {
List<List<Integer>> all; // 存储所有的选择
public List<List<Integer>> combine(int n, int k) {
all = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>(); // 存储当前选择的路径
backTry(list, n, 1, k);// 选择从1到n
return all;
}
public void backTry(List<Integer> list, int n, int idx, int k){
if(list.size() == k){ // 结束条件
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=idx;i<=n;i++){ // 遍历所有的选择
// 做选择
list.add(i);
// 递归调用
backTry(list, n, i+1, k);
// 撤回选择
list.remove(list.size()-1);
}
}
}
39. 组合总和
class Solution {
// 数组无重复元素,同一个数字可以重复选择,选出组合和为target的所有组合
List<List<Integer>> all;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
all = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>();
backTry(list, candidates, target, 0); // 从位置0,空list开始选择
return all;
}
public void backTry(List<Integer> list, int[] candidates, int target, int idx){
if(target == 0){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于target了
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=idx; i<candidates.length;i++){
if((target - candidates[i])>=0){ // 是否满足选择条件
// 做选择
list.add(candidates[i]);
target -= candidates[i];
// 递归调用
backTry(list, candidates, target, i);
// 撤回选择
list.remove(list.size()-1);
target += candidates[i];
}
}
}
}
40. 组合总和 II
class Solution {
// 数组有重复元素,同一个数字只能选择一次,选出组合和为target的所有组合
List<List<Integer>> all;
boolean[] visited;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
all = new ArrayList<List<Integer>>();
visited = new boolean[candidates.length];
List<Integer> list = new ArrayList<Integer>();
Arrays.sort(candidates);
backTry(list, candidates, target, 0); // 从位置0,空list开始选择
return all;
}
public void backTry(List<Integer> list, int[] candidates, int target, int idx){
if(target == 0){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于target了
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=idx; i<candidates.length;i++){
// 是否满足选择条件
// 如果该数字大于target,或者已经被访问过,或者等于前一个数字,且前一个数字没有被访问过
if(candidates[i]>target || visited[i] || i>0 && candidates[i]==candidates[i-1] && !visited[i-1]){continue;}
// 做选择
list.add(candidates[i]);
target -= candidates[i];
visited[i] = true;
// 递归调用
backTry(list, candidates, target, i+1);
// 撤回选择
list.remove(list.size()-1);
target += candidates[i];
visited[i] = false;
}
}
}
216. 组合总和 III
class Solution {
// 从数字1~9中选择k个和为n的组合,每个数字最多使用一次
List<List<Integer>> all;
public List<List<Integer>> combinationSum3(int k, int n) {
all = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList<Integer>();
backTry(list, 1, n, k); // 从数字1,空list开始选择
return all;
}
public void backTry(List<Integer> list, int idx, int n, int k){
if(n == 0 && list.size() == k){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于n了
all.add(new ArrayList<Integer>(list));
return;
}
for(int i=idx;i<=9;i++){
// 是否满足选择条件: 如果该数字大于n
if(i>n){continue;}
// 做选择
list.add(i);
n -= i;
// 递归调用
backTry(list, i+1, n, k);
// 撤回选择
list.remove(list.size()-1);
n += i;
}
}
}