go回溯三要点:闭包复用状态、深拷贝path再存入res、递归入口必做终止判断;闭包避免多参数传递,切片需显式拷贝,剪枝逻辑应置于for条件,终止检查不可省略,去重需排序+!visited[i-1],path撤销不可遗漏。

Go 里写回溯,核心就三件事:用闭包复用状态、每次存结果前深拷贝 path、递归入口必须做终止判断——漏掉任意一条,轻则结果错乱,重则爆栈 panic。
为什么闭包比独立函数更合适?
当状态变量超过 2–3 个(比如 path、used、sum、target),独立函数签名会迅速膨胀,调用时传参极易出错。闭包直接捕获外部变量,逻辑更紧凑。
- 但注意:
path是切片,底层数组被所有递归层共享;res = append(res, path)存的是指针,不是副本 - 正确做法是每次保存前显式深拷贝:
tmp := make([]int, len(path)); copy(tmp, path); res = append(res, tmp) -
slices.Clone(path)(Go 1.21+)语义等价,但底层仍是make + copy,调试时不如手动写清楚
剪枝必须压进 for 循环条件,不能只靠 if 判断
比如 combine(n, k) 中,若还剩 d := k - len(path) 个数要选,那当前最大可选起始位置是 n - d + 1。把这个逻辑放进 for 的终止条件,才能真正跳过无效分支。
- 错误写法:
for i := start; i n-d+1 { continue } ... }—— 循环已启动,栈帧已分配 - 正确写法:
for i := start; i —— 根本不进循环,省掉分配、调用、回退三层开销 - 画个小树验证:第 0 层要选 3 个数,n=5 → d=3 → i 最大只能到 5-3+1=3,否则后面不够填
递归前不加终止判断,十有八九爆栈
常见报错 runtime: goroutine stack exceeds 1000000000-byte limit,根本原因不是递归太深,而是该停的时候没停——等进了下一层才发现 len(path) == k 或 sum > target,已经晚了。
- 必须在调用
dfs前检查:if len(path) == k { res = append(res, clone(path)); return } -
if sum > target { return }和if i == n { return }同理,这些是保命底线,不是锦上添花 - 别指望 for 条件“挡”住所有非法路径;它只管分支数量,不管语义合法性
含重复元素时,去重关键在 visited[i-1] == false
输入是 []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 其实可以放在第一位
最易被忽略的其实是 path 撤销动作:path = path[:len(path)-1] 看似简单,但一旦漏写、多写或写成 path = path[0:len(path)-1],后续所有路径都会错位。建议在递归入口加日志:fmt.Printf("enter: %v\n", path),一眼看出哪层没撤干净。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











