如何将数组中所有的0全部移动到队尾呢?

128 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情
无论前方的困难多么大,我们也要像大圣一样,勇往直前

解题

这道题看上去很简单,将数组中所有的0全部移动到队尾就好了,那么我们可以定义一个函数,将数组中所有非0的数字push到一个数组中,然后将所有的0push到另外一个数组中,最后将两个数组拼接就好了。下面展示代码:

function moveZero(arr: number[]):number[]{
  const arr1: number[] = [];
  const arr2: number[] = [];
  for(let i=0; i<arr.length; i++){
    if(arr[i] != 0){
      arr1.push(arr[i])
    }else{
      arr2.push(arr[i])
    }
  }
  return arr1.concat(arr2)
}

功能测试

const moveZeroArr1 = [1,0,3,4,0,6,8,0,9]
const newMoveZeroArr1 = moveZero(moveZeroArr1);
console.log(newMoveZeroArr1)//[1, 3, 4, 6, 8, 9, 0, 0, 0]

符合预期,说明我们的函数是合格的。

增加条件

在题目要求中,加入一个条件:不的产生新数组
加入这个条件,上面的办法我们就不可以使用了。那我们改如何操作呢?
不产生新的数组的操作方法主要有:push,pop,shift,unshift,splice,sort,reverse。而在这道题目中,pop,shift,sort,reverse这六个办法肯定不满足条件,那么splice可能能够使用。那么我们是不是可以将数组中的0删除掉的同时在数组最后push插入一个0呢,如果要求0在数组最前方的话,就可以利用unshift了。让我们尝试一下。

function moveZero2(arr: number[]):void{
  const length = arr.length;

  let zeroLength = 0;//数组中0有多少个
  for(let i = 0; i < length - zeroLength; i++){
    if(arr[i] == 0){
      arr.splice(i,1);
      arr.push(0)
      i--;
      zeroLength++
    }
  }
}

功能测试

const moveZeroArr2 = [1,3,0,2,6,0,5,9,0,0,7,8];
moveZero2(moveZeroArr2)
console.info(moveZeroArr2)//[1, 3, 2, 6, 5, 9, 7, 8, 0, 0, 0, 0]

符合预期,说明我们的函数是合格的。
在函数代码中应该注意两点:

  • 第一点是在找到0,将其操作后要进行i--操作,否则,当出现连续的两个或者更多的0的情况,就会出现不符合预期的结果,也就是函数会不满足条件
  • 第二点就是length-zeroLength和zeroLength++的操作,为什么要记录0有多少个呢?那是因为,如果我们不这样操作,函数就会进入死循环,当数组第一次变成[1, 3, 2, 6, 5, 9, 7, 8, 0, 0, 0, 0]的时候,其肯定循环次数小于arr.length。这个时候碰到0继续执行,就会i--,然后循环中执行i++,从而导致死循环

复杂度分析

空间复杂度是0(n),因为修改的就是原数组,未发生显得变化。
时间复杂度是O(n^2),因为我们在for循环中使用了splice来操作数组,splice本身就是O(n)的时间复杂度,所以最后函数的时间复杂度是O(n^2),这是一个标糟糕的情况,这样数据量太大会导致我们的执行环境崩溃的,我们需要继续进行优化

优化

在不可以产生新的数组的情况下,我们是不是可以循环这个数组,当循环碰到0的时候,将0的下标记录下来i,然后继续循环,当循环到0后边第一个不是0的数字的下标j的时候,将i和j互相交换。然后继续循环

function moveZero3(arr: number[]):void{
  const length = arr.length;

  let i;
  let j = -1;
  for(i = 0; i < length; i++){
    if(length == 0) return 

    if(arr[i] == 0 && j == -1){//说明是第一次碰到0
      j = i;
    }

    if(arr[i] !=0 && j > 0){//说明不是第一次碰到0
      let n = arr[i];
      arr[i] = arr[j];
      arr[j] = n;
      j++
    }
  }
}

功能测试

const moveZeroArr3 = [1,3,0,2,6,0,5,9,0,0,7,8];
moveZero3(moveZeroArr3)
console.info(moveZeroArr3)//[1, 3, 2, 6, 5, 9, 7, 8, 0, 0, 0, 0]

复杂度分析

空间复杂度依然是0(n);
时间复杂度是0(n):优化后只存在一个循环,不存在时间复杂度大于0(1)级别的js方法,综合考虑其时间复杂度是0(n)

性能对比

console.time('moveZero2');
const arrTestMoveZero2 = [];
for(let i = 0; i < 20*10000; i++){
  if(i % 20 == 0){
    arrTestMoveZero2.push(0)
  }else{
    arrTestMoveZero2.push(i)
  }
}
moveZero2(arrTestMoveZero2)
console.timeEnd('moveZero2');//moveZero2: 120.05712890625 ms

console.time('moveZero3');
const arrTestMoveZero3 = [];
for(let i = 0; i < 20*10000; i++){
  if(i % 20 == 0){
    arrTestMoveZero3.push(0)
  }else{
    arrTestMoveZero3.push(i)
  }
}
moveZero3(arrTestMoveZero3)
console.timeEnd('moveZero3');//moveZero3: 7.47412109375 ms

通过上方的运行时间对比,我们可以发现,两个函数的执行时间就不是一个数量级的。优化后的好很多,而这种优化后的方法是用了双指针的思想。