一起养成写作习惯!这是我参与「掘金日新计划 · 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
通过上方的运行时间对比,我们可以发现,两个函数的执行时间就不是一个数量级的。优化后的好很多,而这种优化后的方法是用了双指针的思想。