问题描述
昨天,我正在为一家公司在筛选前提出的问题而苦恼。问题是你会得到一个整数数组。您的任务是将所有值为 0 的元素移动到数组的中间。为方便起见,center = floor(array.length/2)。
我已经有了一个解决方案,但它仅适用于数组中只有一个零元素的情况。
希望得到大家更好的回答。
谢谢。
/**
*
* @param {*} arr: a list of integers
* @return: updated list of integers with all zero element moved to the middle
* @example: [4,1,3] => [4,3]
*/
const zeroesToMid = (arr) => {
const mid = Math.floor(arr.length/2)
let result
for(let i = 0;i < arr.length;i++){
if(arr[i] === 0) {
let firstHalf = arr.splice(0,i)
let secondHalf = arr.splice(i,arr.length)
result = firstHalf.concat(secondHalf)
result.splice(mid,0)
}
}
return result
}
解决方法
关于您的尝试的一些评论:
-
在每次迭代中,您以基于
result
的arr
重新开始。所以实际上你会丢失之前每次迭代的结果。 -
splice
将移动多个数组项:因此这不是很有效,但您在每次迭代中都会多次调用它。此外,您在这里至少有一个概念错误:let firstHalf = arr.splice(0,i) let secondHalf = arr.splice(i,arr.length)
第一个
splice
实际上会从arr
中删除那些条目(使数组更短),因此对splice
的第二次调用将没有想要的结果。也许您在这里(但不是在您的代码中)将splice
与slice
混淆了。 -
如果前面的两个错误被纠正了,那么仍然是这样:在一个零从索引
i
移动到 forward 位置之后,现在还有另一个值在arr[i]
。但由于循环的下一次迭代将递增i
,您将永远不会查看该值,该值也可能为零。 -
concat
创建一个新数组,并在每次迭代中调用它,使其效率更低。
解决方案
比拼接和连接更好的是在不影响数组大小的情况下复制值。
如果非零值必须保持其原始顺序,那么我会提出以下算法:
在第一次迭代中,您可以计算零的数量。由此您可以推导出它们应该在结果中的范围分组。
在第二次迭代中,您将复制一定数量的 op 非零值到数组的左侧。同样,您可以随意在右侧执行此操作。最后,只需用零填充中间部分。
所以这是一个实现:
function moveZeroes(arr) {
// count zeroes
let numZeroes = 0;
for (let i = 0; i < arr.length; i++) numZeroes += !arr[i];
// Determine target range for those zeroes:
let first = (arr.length - numZeroes) >> 1;
let last = first + numZeroes - 1;
// Move some non-zero values to the left of the array
for (let i = 0,j = 0; i < first; i++,j++) {
while (!arr[j]) j++; // Find next non-zero value
arr[i] = arr[j]; // Move it to right
}
// Move other non-zero values to the right of the array
for (let i = arr.length - 1,j = i; i > last; i--,j--) {
while (!arr[j]) j--; // Find next non-zero value
arr[i] = arr[j]; // Move it to right
}
// Fill the middle section with zeroes:
for (let i = first; i <= last; i++) arr[i] = 0;
return arr;
}
// Demo
console.log(moveZeroes([0,1,2,3,4,5,6,0]));
注意:如果没有非零值保持其原始顺序是必要的,那么您可以进一步减少赋值次数,因为您只需要移动非零值出现在零应该出现的区域中的值。其他非零值可以保持原样。
,可能的方法:
问题陈述没有规定,是否所有的零都应该完全居中(这需要先计算零),所以我们可以只挤压左右部分,将非零元素移动到相应的末尾。
例如,左半部分可能会像这个草图一样处理
cz = 0;
for(let i = 0; i <= mid;i++) {
if (A[i])
A[i-cz] = A[i];
else
cz++;
}
for(let i = mid - cz + 1; i <= mid;i++) {
A[i] = 0;
}
现在增加 mid
if cz>0
(所以我们已经用零填充了中心位置)并对右半部分反向执行相同的操作(反向循环,A[i+cz] = A[i]
) .
结果:
[0,0] => [1,6]
,
一如既往,trincot 对您的代码有什么问题进行了现场分析。
不过,我认为有一个更简单的解决方案。我们可以使用 Array.prototype.filter
选择非零条目,然后将其分成两半,然后通过将前半部分与适当数量的零和后半部分组合来创建输出数组。它可能看起来像这样:
const moveZeroes = (
ns,nonZeroes = ns .filter (n => n !== 0),len = nonZeroes .length >> 1
) => [
... nonZeroes .slice (0,len),... Array (ns .length - nonZeroes .length) .fill (0),... nonZeroes .slice (len)
]
const xs = [0,0]
console .log (... moveZeroes (xs))
console .log (... xs)
首先,nonZeroes .length >> 1
利用位操作来创建比 Math .floor (nonZeroes .length / 2)
更简单的表达式。它也像稍微快一点。但它们具有相同的行为。
注意输出中的原始数组没有改变。这是非常有意的。我只是更喜欢使用不可变数据。但是如果你真的想改变原来的,我们可以使用Object.assign
,将这些新值折叠回原始数组:
const moveZeroes = (
ns,len = nonZeroes .length >> 1
) => Object .assign (ns,[
... nonZeroes .slice (0,... nonZeroes .slice (len)
])
const xs = [0,0]
console .log (... moveZeroes (xs))
console .log (... xs)
最后,我更喜欢这里展示的仅使用表达式而不使用语句的风格。但是如果你不喜欢额外变量的默认参数,这可以很容易地重写如下:
const moveZeroes = (ns) => {
const nonZeroes = ns .filter (n => n !== 0)
const len = nonZeroes .length >> 1
return [
... nonZeroes .slice (0,... nonZeroes .slice (len)
]
}
再说一次,如果我们愿意,我们可以使用 Object .assign
。
这里的大窍门是我们实际上并没有移动值,而是提取其中的一些值,然后简单地将三个单独的数组粘在一起。