go回溯需闭包捕获状态、显式深拷贝切片、剪枝嵌入for条件三者缺一不可:闭包避免参数失控,深拷贝防止共享底层数组,剪枝前置防爆栈。

Go 里写回溯,闭包 + 显式深拷贝 + 循环条件剪枝是铁三角,漏掉任意一环,结果就错、输出就乱、运行就爆栈。
为什么用闭包写 backtrack 而不是独立函数
状态变量一多(比如 path、used、sum、target),独立函数签名立刻失控:func backtrack(path []int, used []bool, sum, target, start int)——调用时极易传错顺序或漏参数。闭包直接捕获外部变量,逻辑紧凑,可读性高。
但代价很实在:path 是切片,所有递归层共享同一底层数组。不深拷贝就存进 res,最后 res 里全是空切片或最后一组值。
- 错误写法:
res = append(res, path) - 正确写法:
tmp := make([]int, len(path)); copy(tmp, path); res = append(res, tmp) - Go 1.21+ 可用
slices.Clone(path),语义清晰,但底层仍是make + copy,调试时不如显式写出来直观
for 循环终止条件里必须塞剪枝逻辑
剪枝不是“加个 if 判断就行”,而是要压进 for 的上限表达式里。否则循环已启动,栈帧已分配,再 return 也晚了。
比如 combine(n, k) 中,还差 d := k - len(path) 个数要选,那当前最大可选起始位置是 n - d + 1:
- 错误位置:
for i := start; i n-d+1 { break } ... } - 正确写法:
for i := start; i
这个公式不是硬背的:画一棵小树,标出第几层、还剩几个要选、当前最多能从哪开始选,推一遍就清楚了。数值类剪枝(如和超限)也建议放这里:for i := start; i 。
递归入口第一行就得做终止判断
报 runtime: goroutine stack exceeds 1000000000-byte limit?十有八九是没在调用前检查该停不该停。
这些判断必须写在 backtrack 函数开头,不能只靠 for 条件“挡”或等进下一层再判:
if len(path) == k { res = append(res, clone(path)); return }if sum > target { return }if i == n { return }
注意:for 条件管的是“分支数量”,这些判断管的是“语义合法性”,两者缺一不可。输入规模可能超 1000 时,直接放弃递归,改用显式栈:stack []struct{ path []int; start int; sum int } 模拟。
含重复元素时,去重关键在 !visited[i-1]
输入是 []int{1,1,2},不处理会得到两个相同的 [1,1,2]。重点不是“值相等就跳”,而是“同层重复才跳”。
前提必须先 sort.Ints(nums),否则 i > 0 && nums[i] == nums[i-1] 无意义。维护 visited []bool,跳过条件是:
- 正确写法:
i > 0 && nums[i] == nums[i-1] && !visited[i-1] - 错误写法:
if i > 0 && nums[i] == nums[i-1] { continue }——这会砍掉所有合法分支,比如第二个1其实可以放在第一位
真正难的不是写出逻辑,而是每次写完都得问自己:这一步深拷贝了吗?剪枝压进 for 条件了吗?撤销写对了吗?这些点不写进注释,下次看自己代码也得卡住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











