回溯算法的思想:采用试错的思想,它尝试分步的去解决一个问题,遇到“南墙”后,回退,再寻找其他的答案。一般的,需要一个动态数组保存所有成功的结果、还有一个数组保存每次的结果,在遍历过程中需要有终止条件。有些时候加上剪枝的话,会大大减小复杂度。
下面是几道leetcode上的回溯题
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution {
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
// 动态数组保存每次的结果
List<List<Integer>> ans = new ArrayList<>();
if(nums == null) return ans;
// 判断数字是否被用过
boolean[] used = new boolean[n];
List<Integer> path = new ArrayList<>();
dfs(ans, path, nums, used, n, 0) ;
return ans;
}
public void dfs(List<List<Integer>> ans, List<Integer> path, int[] nums, boolean[] used, int n, int depth){
// 终止条件
if(depth == n){
ans.add(new ArrayList<>(path));
return;
}
// 循环迭代
for(int i = 0; i < n; i++){
if(used[i] == false){
path.add(nums[i]);
used[i] = true;
dfs(ans, path, nums, used, n, depth + 1);
// 退回
used[i] = false;
path.remove(path.size() - 1);
}
}
}
}
47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
- 总体思路与上题差不多,为了排除重复的排列,加了个剪枝条件
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int n = nums.length;
boolean[] used = new boolean[n];
back(nums, ans, path, used, 0, n);
return ans;
}
public void back(int[] nums, List<List<Integer>> ans, List<Integer> path, boolean[] used, int deep, int n){
if(deep == n){
ans.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < n; i++){
if(used[i] == true) continue;
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) continue;
if(used[i] == false){
path.add(nums[i]);
used[i] = true;
back(nums, ans, path, used, deep + 1, n);
used[i] = false;
path.remove(path.size() - 1);
}
}
}
}
39. 组合总和
数字和为目标
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
int sum = 0, index = 0;
dfs(candidates, target, sum, index);
return ans;
}
public void dfs(int[] candidates, int target, int sum, int index){
if(sum > target) return;
if(sum == target){
ans.add(new ArrayList<>(path));
return;
}
for(int i = index; i < candidates.length; i++){
path.add(candidates[i]);
dfs(candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1);
}
}
}
40. 组合总和 II
要求每个数字在每个组合中只能使用一次并且解集不能包含重复的组合。
- 与47.全排列 II类似,加个剪枝条件,并且注意不要重复选取同一个数
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(candidates);
int len = candidates.length;
int sum = 0;
List<Integer> path = new ArrayList<>();
dfs(ans, sum, path, target, 0, candidates);
return ans;
}
public void dfs(List<List<Integer>> ans, int sum, List<Integer> path, int target, int index, int[] candidates){
if(sum > target) return;
if(sum == target){
ans.add(new ArrayList<>(path));
return;
}
for(int i = index; i < candidates.length; i++){
// 剪枝是为了满足不能包含重复的组合
if(i > index && candidates[i] == candidates[i - 1]) continue;
path.add(candidates[i]);
// i + 1 是为了满足每个数字在每个组合中只能使用一次
dfs(ans, sum + candidates[i], path, target, i + 1, candidates);
path.remove(path.size() - 1);
}
}
}
解题思路也参考了很多leetcode中的题解,感谢!!
如果文章有什么问题,欢迎指出~~