You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)为例:

  1. 第一轮循环结束后ans为[[1]]
  2. 执行ans = [...ans, ...ans]后ans变为[[1], [1]],此时两个[1]是同一个数组的引用
  3. 如果用ans[i].push(num)修改后半部分的元素,会同时修改前半部分对应的元素,最终两个位置的数组都变成[1,2],直接违反预期
  4. 如果用ans[i] = [...ans[i], num],只会给后半部分位置赋值新的数组[1,2],前半部分的[1]完全不受影响,符合逻辑
    后续多轮循环都会重复这个错误,同一个数组被多次修改,最终就会出现元素重复、内容错乱的输出。

替代方案

如果要保留push的写法,可以在拷贝时对数组元素做一层深复制,示例如下:

ans = [...ans, ...ans.map(item => [...item])]

此时前后两部分的元素指向不同的数组对象,push操作不会互相影响。

内容的提问来源于stack exchange,提问作者sssyn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 13:06:05