参考大佬:5.6 回溯算法总结 | 算法吧 (suanfa8.com)
目录
题型一:排列、组合、子集相关问题
特别注意:此类题目要分析是否要对数组进行排列!!!!
1.46. 全排列
2.47. 全排列 II
3.好好体会大佬做法**必看** 78. 子集
4.90. 子集 II
5.77. 组合
题型一:排列、组合、子集相关问题
特别注意:此类题目要分析是否要对数组进行排列!!!!
1.46. 全排列
package main;
import java.io.*;
import java.math.*;
import java.util.*;
import javax.swing.text.StyledEditorKit.BoldAction;
public class Main {
//[1,2,3]的全排列
//编写全排列的函数
public ArrayList
int len=nums.length;
ArrayList
//特判
if(len==0) {
return res;
}
boolean[] used=new boolean[len];//标记遍历的结点是否使用过
ArrayList
dfs(nums, len, 0, used, path, res);
return res;
}
//回溯算法DFS
public void dfs(int[] nums,int len,int depth,//depth:代表问题转换后的层数 也代表和着path要填写的元素下标
boolean[] used,ArrayList
//当path当中已经完成了从根节点到叶子结点的一次延伸
if(path.size()==len) {
res.add(new ArrayList<>(path));
return;
}
//遍历used数组获取未使用过的结点
for (int i = 0; i < used.length; i++) {
if(!used[i]) {
//将当前结点加入path变量
path.add(nums[i]);
used[i]=true;
//从当前结点再开始DFS
dfs(nums, len, depth+1, used, path, res);
//当前结点DFS完成之后要恢复到原状态
path.remove(path.size()-1);
used[i]=false;
}
}
}
public static void main(String[] args) {
Main test=new Main();
int[] nums=new int[] {1,2,3};
ArrayList
System.out.println(ans);
}
}
2.47. 全排列 II
此题就开始了剪枝!好好体会剪枝如何设置!
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if(i>0&&nums[i-1]==nums[i]&&!used[i-1]) {
continue;
}
class Solution {
public List> permuteUnique(int[] nums) {
int len=nums.length;
List> res=new ArrayList
>();
if(len==0) {
return res;
}
Arrays.sort(nums);
boolean[] used=new boolean[len];
ArrayList
dfs(nums,len,0,used,path,res);
return res;
}
private void dfs(int[] nums, int len, int depth, boolean[] used, ArrayList> res) {
if(path.size()==len) {
res.add(new ArrayList
return;
}
LOOP:for (int i = 0; i < used.length; i++) {
if(!used[i]) {
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if(i>0&&nums[i-1]==nums[i]&&!used[i-1]) {
continue;
}
path.add(nums[i]);
used[i]=true;
dfs(nums, len, depth+1, used, path, res);
used[i]=false;
path.remove(path.size()-1);
}
}
}
}
3.好好体会大佬做法**必看** 78. 子集
自己思考做法:本题我们首先画出树形图,编写剪枝条件
通过画出树形图 我可以看出所剪枝的都是如果当前path的最后一个元素如果大于要新加入则不进行添加continue继续循环。
自己所写代码如下:
class Solution {
public List> subsets(int[] nums) {
int len=nums.length;
List> res=new ArrayList
>();
res.add(new ArrayList<>());
if(len==0) return res;
ArrayList
boolean[] used=new boolean[len];
dfs(nums,len,0,used,path,res);
return res;
}
private void dfs(int[] nums, int len, int depth, boolean[] used, ArrayList> res) {
// TODO Auto-generated method stub
if(path.size()==len) return;
for (int i = 0; i < len; i++) {
if(!used[i]) {
if(path.size()>0) if(path.get(path.size()-1)>nums[i]) continue;
path.add(nums[i]);
res.add(new ArrayList
used[i]=true;
dfs(nums, len, depth+1, used, path, res);
used[i]=false;
path.remove(path.size()-1);
}
}
}
}
大佬则采用了一种更巧妙的方法!!核心如下
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Solution {
public List> subsets(int[] nums) {
List> res = new ArrayList<>();
int len = nums.length;
if (len == 0) {
return res;
}
Stack
dfs(nums, 0, len, stack, res);
return res;
}
private void dfs(int[] nums, int index, int len,
Stack> res) {
if (index == len) {
res.add(new ArrayList<>(stack));
return;
}
// 当前数可选,也可以不选
// 不选,直接进入下一层
dfs(nums, index + 1, len, stack, res);
// 选了有,进入下一层
stack.add(nums[index]);
dfs(nums, index + 1, len, stack, res);
stack.pop();
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
Solution solution = new Solution();
List> subsets = solution.subsets(nums);
System.out.println(subsets);
}
}
private void find(int[] nums, int begin, List
// 没有显式的递归终止
res.add(new ArrayList<>(pre));// 注意:Java 的引用传递机制,这里要 new 一下
for (int i = begin; i < nums.length; i++) {
pre.add(nums[i]);
find(nums, i + 1, pre);
pre.remove(pre.size() - 1);// 组合问题,状态在递归完成后要重置
}
// res.add(new ArrayList<>(pre));放到这里就是后序遍历结果!!
}
import java.util.ArrayList;
import java.util.List;
public class Solution {
private List> res;
private void find(int[] nums, int begin, List
// 没有显式的递归终止
res.add(new ArrayList<>(pre));// 注意:Java 的引用传递机制,这里要 new 一下
for (int i = begin; i < nums.length; i++) {
pre.add(nums[i]);
find(nums, i + 1, pre);
pre.remove(pre.size() - 1);// 组合问题,状态在递归完成后要重置
}
}
public List> subsets(int[] nums) {
int len = nums.length;
res = new ArrayList<>();
if (len == 0) {
return res;
}
List
find(nums, 0, pre);
return res;
}
}
4.90. 子集 II
经过上面的题目的洗礼,我们可以有多种思路拿下此题!
方法一:固定思维:
我们先画出树形图,来观察为什么,以及何时需要剪枝
观察剪枝条件:1.与前一个撤销的值不能再次添加2.不能添加小于当前path最后一个数值的元素
还要额外分析此题是否需要提前对传入的数组进行排序:需要去重所以排序
有了前面的基础轻松拿下!!
class Solution {
public List> subsetsWithDup(int[] nums) {
int len=nums.length;
List> res=new ArrayList
>();
Deque
res.add(new ArrayList<>(path));
if(len==0) return res;
Arrays.sort(nums);
boolean[] used=new boolean[len];
dfs(nums,len,0,used,path,res);
return res;
}
private void dfs(int[] nums, int len, int depth, boolean[] used, Deque> res) {
// TODO Auto-generated method stub
if(path.size()==len) return;
for (int i = 0; i < len; i++) {
if(!used[i]) {
if(i>0&&nums[i]==nums[i-1]&&!used[i-1]
||path.size()>0&&path.getLast()>nums[i]) {
continue;
}
path.addLast(nums[i]);
res.add(new ArrayList<>(path));
used[i]=true;
dfs(nums, len, depth+1, used, path, res);
path.removeLast();
used[i]=false;
}
}
}
}
方法二:学习第三题的两种思维的第二种 选几个元素 然后在进行去掉重复的剪枝即可
同样我们轻松拿下此题!
class Solution {
public List> subsetsWithDup(int[] nums) {
int len=nums.length;
List> res=new ArrayList
>();
Deque
Arrays.sort(nums);
if(len==0) return res;
boolean[] used=new boolean[len];
dfs(nums,len,0,used,path,res);
return res;
}
private void dfs(int[] nums, int len, int begin, boolean[] used, Deque> res) {
res.add(new ArrayList
for (int i = begin; i < len; i++) {
if(!used[i]) {
if(i>0&&nums[i]==nums[i-1]&&!used[i-1]) continue;
path.addLast(nums[i]);
used[i]=true;
dfs(nums, len, i+1, used, path, res);
path.removeLast();
used[i]=false;
}
}
}
}
5.77. 组合
自己思路:
画出树形图:发现就是前面我们所学的结合,只不过将结果集中在了叶子结点,并且这里的输入变量k就是我们要选的元素个数的最大值
我们轻松拿下此题,但是时间方面仍然存在较大的缺陷
class Solution {
public List> combine(int n, int k) {
//先生成数组
int[] nums=new int[n];
int len=n;
for (int i = 0; i < len; i++)
nums[i]=i+1;
List> res=new ArrayList
>();
if(len==0) return res;
Deque
if(len==1) {
path.addLast(nums[0]);
res.add(new ArrayList
return res;
}
dfs(nums,len,0,k,path,res);
return res;
}
private void dfs(int[] nums, int len, int begin, int last, Deque> res) {
// TODO Auto-generated method stub
if(path.size()==last) {//到达所需要的叶子结点
res.add(new ArrayList
return;
}
for (int i = begin; i < nums.length; i++) {
path.addLast(nums[i]);
dfs(nums, len, i+1, last, path, res);
path.removeLast();//回溯
}
}
}
大佬做法:进行了很好的优化,可以节省大量时间
移步大佬题解:回溯算法 + 剪枝(Java) - 组合 - 力扣(LeetCode) (leetcode-cn.com)
6.39. 组合总和
此题未得到解决 参考了大佬的题解
回溯算法 + 剪枝(回溯经典例题详解) - 组合总和 - 力扣(LeetCode) (leetcode-cn.com)
dfs(candidates, i, target-path.getLast(), path, res);
这句话十分的巧妙,我们下一次的起始位置还是i,因为可能会重复选择,而且整个树的向下推进是通过target-path.getLast();来实现!!!!!!
private void dfs(int[] candidates, int begin, int target, Deque> res) {
// TODO Auto-generated method stub
if(target==0) {
res.add(new ArrayList
return;
}
dfs(candidates, i, target-path.getLast(), path, res);
path.removeLast();
}
}
7.40. 组合总和 II
根据上一题的基础很快得出代码:
public List> combinationSum2(int[] candidates, int target) {
int len=candidates.length;
List> res=new ArrayList
>();
if(len==0) return res;
Arrays.sort(candidates);
boolean[] used=new boolean[len];
Deque
dfs(candidates,0,target,used,path,res);
return res;
}
private void dfs(int[] candidates, int begin, int target,boolean[] used, Deque> res) {
// TODO Auto-generated method stub
if(target==0) {
res.add(new ArrayList
return;
}
for (int i =begin; i < candidates.length; i++) {
if(target<0) break;
if(i>0&&candidates[i]==candidates[i-1]&&!used[i-1]) continue;
path.add(candidates[i]);
used[i]=true;
dfs(candidates, i+1, target-path.getLast(),used, path, res);
path.removeLast();
used[i]=false;
}
}
大佬题解如下: 40. 组合总和 II 题解 - 力扣(LeetCode) (leetcode-cn.com)
文章来源
发表评论