回溯算法秒杀所有排列/组合/子集问题 :: labuladong的算法小抄 #999
Replies: 80 comments 13 replies
-
顶礼膜拜,太牛逼了! |
Beta Was this translation helpful? Give feedback.
-
感谢大佬的文章。大佬的文章最强的地方在于,通过归纳总结框架,高度抽象出来了一类问题。比如要不要重复选,其实就取决于下一次递归是i还是i+1。很好。 关于全排列去重,个人觉得有一个更好理解的方案。按照回溯框架的思想,每次递归的时候遍历可选列表。什么时候会造成答案重复呢?在同一个位置上,同样值的元素被选了多次。比如[2, 2],如果选了第一个2,下次还选2,就会造成重复。为了避免这个重复,可以在遍历的时候判断一下现在要选的元素是否和上一次选择过的相等。相等就跳过。伪代码如下:
|
Beta Was this translation helpful? Give feedback.
-
@BIT-zhaoyang 这个思路也不错!我的代码: void backtrack(int[] nums, LinkedList<Integer> track) {
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
// 记录之前树枝上元素的值
// 题目说 -10 <= nums[i] <= 10,所以初始化为特殊值
int prevNum = -666;
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (used[i]) {
continue;
}
if (nums[i] == prevNum) {
continue;
}
track.add(nums[i]);
used[i] = true;
// 记录这条树枝上的值
prevNum = nums[i];
backtrack(nums, track);
track.removeLast();
used[i] = false;
}
} |
Beta Was this translation helpful? Give feedback.
-
第 40 题「 组合总和 II」的backtrack在 往track里添加元素的代码是不是写错了, 应该是 |
Beta Was this translation helpful? Give feedback.
-
@TimurJiangShan 是写错了,已修复,感谢指正 |
Beta Was this translation helpful? Give feedback.
-
讲的很好,点个赞!另外有两个格式的小问题,
|
Beta Was this translation helpful? Give feedback.
-
@zhyozhi 感谢指正,已修复~ |
Beta Was this translation helpful? Give feedback.
-
第39题的回溯部分应该是i而不是i +1吧?不然没法重复选择啊。一点拙见,如果不对的话希望能指正一下,谢谢大神! class Solution:
def __init__(self):
self.res = []
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
track = []
self.backtrack(candidates, target, 0, track, 0)
return self.res
def backtrack(self, candidates, target, start, track, trackSum):
if trackSum == target:
self.res.append(copy.deepcopy(track))
return
elif trackSum > target:
return
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i - 1]:
continue
track.append(candidates[i])
trackSum += candidates[i]
self.backtrack(candidates, target, i, track, trackSum)
track.pop()
trackSum -= candidates[i] |
Beta Was this translation helpful? Give feedback.
-
@yuhaoban 你看错了吧,我写的就是 |
Beta Was this translation helpful? Give feedback.
-
class Solution {
List<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backTrack(n,k,0,1);
Iterator<List<Integer>> iterator = result.iterator();
while(iterator.hasNext()){
if(iterator.next().size() != k){
iterator.remove();
}
}
return result;
}
private void backTrack(int target,int k,int sum,int start){
if(sum == target){
result.add(new LinkedList<>(track));
return;
}
if(sum > target){
return;
}
for(int i = start; i<= 9; i++){
sum += i;
k--;
track.add(i);
backTrack(target,k,sum,i+1);
track.removeLast();
k++;
sum -= i;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
发现对于排列(元素可重不可复选)的剪枝部分,使用 |
Beta Was this translation helpful? Give feedback.
-
即使
|
Beta Was this translation helpful? Give feedback.
-
@sxcnmslll @FZ20192019 其实你们的说法都不太准确,我把对 |
Beta Was this translation helpful? Give feedback.
-
第 47 题「 全排列 II」也可以尝试,不用used list def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def backtrack(path, nums):
if not nums:
res.append(path)
return
for i, num in enumerate(nums):
if i > 0 and nums[i] == nums[i-1]:
continue
backtrack(path + [num], nums[:i] + nums[i+1:])
res = []
nums.sort()
backtrack([], nums)
return res |
Beta Was this translation helpful? Give feedback.
-
东哥,《排列(元素无重可复选)》这种情况下面ChatGPT翻译的CPP代码好像有些问题,它依然还是在用used数组剪枝。 |
Beta Was this translation helpful? Give feedback.
-
关于排列有重复元素问题,为什么要使用!used[i-1],我的一个理解是这样做到了同层剪枝,而父子结构不能剪枝。 |
Beta Was this translation helpful? Give feedback.
-
总结部分,形式三、元素无重可复选,在子集/组合框架模版backtrack(nums,i,track),不是i+1 |
Beta Was this translation helpful? Give feedback.
-
感谢,受益良多。提出一个前两类问题的变体: 例如无重复每元素可用N次,有重复每元素可用N次,某元素可多次使用。这些变体将其考虑展开最终应都可变为有重复每元素仅用一次。 |
Beta Was this translation helpful? Give feedback.
-
请问子集(元素无重不可复选)里第17行加入路径,res.add(new LinkedList<>(track))括号里直接写track可以吗? |
Beta Was this translation helpful? Give feedback.
-
力扣上没有类似的题目,我们不妨先想一下,nums 数组中的元素无重复且可复选的情况下,会有哪些排列? 比如输入 nums = [1,2,3],那么这种条件下的全排列共有 3^3 = 27 种: [ // 注意:go 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。 func permuteRepeat(nums []int) [][]int {
} // 判断一个切片中是否包含某个元素的函数
} 以下这段代码是不是多余的: |
Beta Was this translation helpful? Give feedback.
-
有朋友知道为什么子集/组合中的backtrack方法中参数starter赋值给i参与循环 与 排列中的backtrack方法中循环i直接赋值0,有什么区别呢? |
Beta Was this translation helpful? Give feedback.
-
最后总结,第三部分元素无重可复用,组合问题中,这里代码的进入下层决策的 backtrack(i) 而不是 backtrack(i + 1),和上面讲的不一致。 |
Beta Was this translation helpful? Give feedback.
-
关于 子集/组合(元素可重不可复选),if(i > start && nums[i] == nums[i-1]) continue; 这里的i > start,指的是nums[i]不能是子树中的第一个子树,也就是不能是最左边那棵树,那么这里的判断指的就是:nums[i] 和 nums[i-1]相等,并且nums[i]和nums[i-1]不是父子关系,而是兄弟关系,那么就将nums[i]这条枝剪掉。只保留了第一条枝,后面相同的数字的枝都剪了。 |
Beta Was this translation helpful? Give feedback.
-
回溯法确实玩的很溜,总结概括除了新高度! |
Beta Was this translation helpful? Give feedback.
-
太强了,跟着这篇文章刷题感觉非常nice。刚开始啥也不会,刷了几道题后后面的题几乎就能灵活运用。 |
Beta Was this translation helpful? Give feedback.
-
其实,代码中的path/track,全部的都改成stack,一下子更容易理解了呢。 |
Beta Was this translation helpful? Give feedback.
-
import (
"sort"
)
func combinationSum2(candidates []int, target int) [][]int {
track :=[]int{}
res := [][]int{}
var sum int
sort.Ints(candidates)
var backtrack func(int)
backtrack =func(start int){
if sum == target {
temp:=make([]int,len(track))
copy(temp,track)
res=append(res,temp)
return
}
if (sum>target){
return
}
for i:=start;i<len(candidates);i++{
if i>start && candidates[i]==candidates[i-1]{
continue
}
track=append(track,candidates[i])
sum+=candidates[i]
backtrack(i+1)
track=track[:len(track)-1]
sum-=candidates[i]
}
}
backtrack(0)
return res
} |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:回溯算法秒杀所有排列/组合/子集问题
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions