LeetCode中回溯法的一些题总结
最近刷LeetCode(惭愧,前两页还没刷完···)发现在好多问题中都碰到了回溯法,并且有一个固定的模板。于是想试着总结一下,并加深记忆,希望以后碰到类似的问题能信手拈来~
利用回溯的题目,比较好识别,特点就是需要穷举才能得到答案。所以肯定是需要递归的。(吐槽一下自己,树的问题基本都需要递归,我每次碰到都要想半天···)
话不多说,上题:
- LeetCode46 全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入:[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){
List<List<Integer>> res = new ArrayList<>();
backtrack(res, new ArrayList<>(), nums);
return res;
}
public void backtrack(List<List<Integer>> res, List<Integer> list, int[]nums){
if(list.size() == nums.length){
res.add(new ArrayList<>(list));
} else {
for(int i = 0; i < nums.length; i++){
if(list.contains(nums[i])){
continue;
}
list.add(nums[i]);
backtrack(res, list, nums);
list.remove(list.size()-1);
}
}
}
}
- LeetCode 47 给定一个可包含重复数字的排列,返回所有不重复的全排列
示例:
输入:[1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
和上面的题相比,除了回溯外,为了不引入重复项,需要进行剪枝操作.因此,我们引入一个标记是否被访问过的变量。
图片来自微信公众号Leetcode名企之路
下面是代码
class Solution{
public List<List<Integer>> permuteUnique(int[] nums){
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];//标记是否被访问过
backtrack(res, new ArrayList<>(), nums, used);
return res;
}
public void backtrack(List<List<Integer>> res, List<Integer> list, int[] nums, boolean[] used){
if(list.size() == nums.length){
res.add(new ArrayList<>(list));
} else {
for(int i = 0; i < nums.length; i++){
if(used[i]) continue;
if(i > 0 && nums[i-1] == nums[i] && !used[i - 1]) continue;
used[i] = true;
list.add(nums[i]);
backtrack(res, list, nums, used);
used[i] = false;
list.remove(list.size()-1);
}
}
}
}
- LeetCode77 组合
给定两个整数n和k,返回1~n中所有可能的k个数的组合
示例:
输入:n=4, k= 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4]
]
直接上代码
class Solution{
public List<List<Integer>> combine(int n, int k){
List<List<Integer>> res = new ArrayList<>();
backtrack(res, new ArrayList<>(), n, k, 1);
return res;
}
public void backtrack(List<List<Integer>> res, List<Integer> list, int n, int k, int start){
if(list.size() == k){
res.add(new ArrayList<>(list));
} else{
for(int i = start; i <= n; i++){
list.add(i);
backtrack(res, list, n, k, i+1);
list.remove(list.size()-1);
}
}
}
}
- LeetCode78 子集
代码
class Solution{
public List<List<Integer>> subsets(int[] nums){
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
backtrack(res, new ArrayList<>(), nums, 0);
return res;
}
public void backtrack(List<List<Integer>> res, List<Integer> list, int[] nums, int start){
res.add(new ArrayList<>(list));
for(int i = start; i < nums.length; i++){
list.add(nums[i]);
backtrack(res,list,nums,i+1);
list.remove(list.size()-1);
}
}
}
全排列和子集的区别:全排列的话需要约束每一项的元素个数,而且for循环里永远是从0开始的;子集不需要约束元素个数,因此每个解都要加入到最终解集中去,为了防止重复项,需要设置一个start,标记每次回溯从哪里开始。
代码和图参考公众号LeetCode名企之路,安利一波。