Go中append的异常行为:LeetCode子集问题回溯解法答疑
Go回溯解法中子集问题的切片引用陷阱
问题场景
用Go解决子集问题时,写出了如下回溯解法,但结果不正确:
func subsets(nums []int) [][]int { sol := make([][]int,0) temp:= make([]int,0) var backtrack func(idx int) backtrack = func(idx int) { sol = append(sol, temp) fmt.Println(temp, append([]int{},temp...)) if idx == len(nums) { return } for i:= idx; i<len(nums);i++{ temp = append(temp,nums[i]) backtrack(i+1) temp = temp[:len(temp)-1] } } backtrack(0) return sol }
测试发现必须用append(sol, append([]int{}, temp...))替代sol = append(sol, temp)才能得到正确结果。虽然执行fmt.Println(temp, append([]int{}, temp...))时两者输出内容一致,但原写法无法得到正确结果,需要明确两者的区别及原因。
核心区别:引用 vs 独立副本
- 直接存
temp是存引用:Go的切片是包含「底层数组指针、长度、容量」的结构体。当你把temp追加到sol时,sol里存储的是这个切片结构体的副本,但它指向的底层数组和原temp完全相同。后续回溯过程中,temp会被append添加元素,又被temp[:len(temp)-1]截断,这些操作都会修改共享的底层数组内容。最终sol里所有之前存入的切片,都会指向被反复修改后的底层数组,导致结果全错。 append([]int{}, temp...)是创建独立副本:这个写法会先初始化一个空切片,再把temp的所有元素复制进去,生成一个全新的切片。新切片拥有独立的底层数组,和原temp彻底分离。后续修改temp的内容时,不会影响已经存入sol的这个副本,因此sol里的每个子集都能保持当时的元素状态,最终得到正确结果。
为什么fmt.Println输出看起来一致?
fmt.Println打印切片时,会即时读取切片当前指向的底层数组内容。执行fmt.Println(temp, append([]int{}, temp...))时,这两个操作在同一时间点完成,此时temp的底层数组还没被后续回溯步骤修改,所以两者输出的内容完全相同。但后续回溯修改temp时,sol里的原切片引用会同步变化,而副本不受影响。
内容的提问来源于stack exchange,提问作者yisic80
相关产品推荐
相关产品推荐

