LeetCode子集问题:push修改数组错误、扩展运算符赋值生效原因
JavaScript中push和展开运算符赋值的差异:LeetCode子集问题踩坑分析
问题背景
在解决LeetCode的subsets(子集)问题时,两版核心逻辑一致的代码运行结果差异巨大,唯一区别仅为数组元素的修改方式不同。
错误代码
测试输入:INPUT = [1, 2, 3]
var subsets = function(nums) { let ans = [] for (let num of nums) { // 复制数组 ans = [...ans, ...ans] const size = ans.length // 遍历数组的后半部分 for (let i = size / 2; i < size; i++) { ans[i].push(num) // 错误写法 } ans.push([num]) } return [[], ...ans] };
错误输出:ANS = [[],[1,2,3,3],[1,2,3,3],[2,3],[1,2,3,3],[1,2,3,3],[2,3],[3]]
正确代码
var subsets = function(nums) { let ans = [] for (let num of nums) { // 复制数组 ans = [...ans, ...ans] const size = ans.length for (let i = size / 2; i < size; i++) { ans[i] = [...ans[i], num] // 正确写法 } ans.push([num]) } return [[], ...ans] };
正确输出:ANS = [[],[1],[1,2],[2],[1,3],[1,2,3],[2,3],[3]]
问题根本原因
两种写法的差异本质是JavaScript引用类型的浅拷贝特性,二者的底层执行逻辑完全不同:
ans[i].push(num):直接修改原数组对象的内容,不会生成新的数组ans[i] = [...ans[i], num]:基于原数组的内容生成一个全新的数组对象,再将新对象赋值给ans[i]
具体执行错误过程
执行ans = [...ans, ...ans]时做的是浅拷贝:新数组后半部分的每个元素,和前半部分对应的元素指向内存中同一个数组对象。
以第二轮循环(num=2)为例:
- 第一轮循环结束后ans为
[[1]] - 执行
ans = [...ans, ...ans]后ans变为[[1], [1]],此时两个[1]是同一个数组的引用 - 如果用
ans[i].push(num)修改后半部分的元素,会同时修改前半部分对应的元素,最终两个位置的数组都变成[1,2],直接违反预期 - 如果用
ans[i] = [...ans[i], num],只会给后半部分位置赋值新的数组[1,2],前半部分的[1]完全不受影响,符合逻辑
后续多轮循环都会重复这个错误,同一个数组被多次修改,最终就会出现元素重复、内容错乱的输出。
替代方案
如果要保留push的写法,可以在拷贝时对数组元素做一层深复制,示例如下:
ans = [...ans, ...ans.map(item => [...item])]
此时前后两部分的元素指向不同的数组对象,push操作不会互相影响。
内容的提问来源于stack exchange,提问作者sssyn
相关产品推荐
相关产品推荐

