数组移零至末尾算法问题:现有解法不符合预期求解释
问题描述
你编写了如下moveZeros函数,意图将数组中所有零移至末尾,同时保留非零元素的原有顺序:
const moveZeros = (arr) => { const finalArray = arr; finalArray.forEach((element, i) => { if (element === 0) { finalArray.splice(i, 1); finalArray.push(0); } if (finalArray[i - 1] === 0) { finalArray.splice(i - 1, 1); finalArray.push(0); } }); return finalArray; };
该函数在调用moveZeros([false,1,0,1,2,0,1,3,"a"])时运行正常,但处理长零数组moveZeros([9, 9, 1, 2, 1, 1, 3, 1, 9, +0, 9, +0, +0, +0, +0, +0, +0, +0, +0, +0])时,结果不符合预期(预期结果为[9, 9, 1, 2, 1, 1, 3, 1, 9, 9, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0])。
问题根源
1. forEach遍历与数组修改冲突
forEach是基于数组初始长度遍历的,当你用splice删除元素时,数组长度缩短,后续元素会向前移位,但forEach的索引i仍按初始序列递增,这会导致部分元素被跳过或重复处理。
比如在长数组例子中,第一个零位于索引9,执行splice(9,1)后,原索引10的9会移到索引9的位置,但forEach的下一个索引是10,此时访问的是原索引11的零,直接跳过了刚移过来的9,后续的零处理也会因为索引错位出现遗漏。
2. 多余且错误的第二个判断逻辑
第二个if (finalArray[i - 1] === 0)完全是画蛇添足:
- 当
i=0时,i-1=-1,访问finalArray[-1]得到undefined,不会触发,但随着遍历推进,一旦触发该判断,splice(i-1,1)会再次修改数组,进一步打乱元素位置,导致遍历逻辑彻底混乱,非零元素可能被错误移到末尾。
3. 直接引用原数组
const finalArray = arr;只是把原数组的引用赋值给finalArray,修改finalArray等同于修改原数组,虽然这不是直接导致错误的原因,但会带来副作用(原数组被修改),不符合函数的纯性要求。
正确实现方案
方案一:创建新数组(简洁直观)
先过滤出所有非零元素,再补充对应数量的零,逻辑清晰且无副作用:
const moveZeros = (arr) => { const nonZeroElements = arr.filter(item => item !== 0); const zeroCount = arr.length - nonZeroElements.length; return nonZeroElements.concat(Array(zeroCount).fill(0)); };
方案二:原地修改(内存高效)
使用双指针法,先将所有非零元素移到数组前端,再将剩余位置填充为零:
const moveZeros = (arr) => { let nonZeroIndex = 0; // 移动非零元素到前端 for (let i = 0; i < arr.length; i++) { if (arr[i] !== 0) { arr[nonZeroIndex] = arr[i]; nonZeroIndex++; } } // 填充剩余位置为零 for (let i = nonZeroIndex; i < arr.length; i++) { arr[i] = 0; } return arr; };
这两种方案都能正确处理所有测试用例,包括长零数组,且严格保留非零元素的原有顺序。
内容的提问来源于stack exchange,提问作者SeGvr

