这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
移动零(题号283)
题目
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
链接
解释
这题啊,这题是经典简我击。
虽说是简我击,但确实是需要思考的,如果是没有做过双指针的人,可以有这样的思路来一点点实现最优的效果。
首先,肯定是不能进行排序的,因为排序可能会改变其它元素的位置,这种方案可以被否定了。
接下来想到的可能是扫一遍数组,如果遇到0,就用splice将当前元素干掉,在数组末尾再push一次,这样到最后也可以实现题目想要的效果,而且splice和push都是在原数组上进行操作,并没有别的问题。
需要注意的就是需要累计0的次数,这样的结束条件可以避免无必要的操作,减少循环次数。
这种方法当然是可行的,但在性能上依旧有些问题,因为用了splice和push操作,有没有办法可以直接通过数组元素值的替换来实现需求呢?
显然是有的。
是不是可以这样做,首先从右到左遍历数组,累计遇到0的此时,这里就叫count了。
在遍历内部,如果当前的数字为0,那么需要将从当前位置开始,到数组末尾的这一段位置的所有数字位置都左右替换一次,其实也不用到数组末尾,应该是到数组末尾的长度上减去count的长度。
是不是有点不太好理解?举个例子:
[0, 1, 0, 3, 12]
从右往左走,12和3都没有问题,遇到index为2的0时,开始进行左右交换,也就是0和3交换,现在3的位置上是0了,再将其和12进行交换,此时0就在数组的末尾了,count变量也要自增一,以后如果再遇到0就不用处理到数组末尾的0了。
此时处理完成的数据应该是:
[0, 1, 3, 12, 0]
继续遍历,遇到1是没有问题的,遇到index为0时,再开始进行从当前位置开始的数据交换。
这样一直交换到数组的第一个元素,遍历就结束了,答案也就出来了。
这种做法并没有使用数组的特殊API,比方说splice和push,但重复遍历的元素较多,如果数组前面0比较多,需要处理的次数也会较多。
那有没有什么更简单的办法呢?不需要处理这么多重复的数据。
显然还是有的,这就是官方正解的双指针了。
前面两种方法都是根据0来进行处理的,让我们换个思路,处理非0数字。
先搞两个指针,左指针和右指针,都从0开始。
右指针会从左往右一次遍历,如果遇到非0数字,将这个数字和左指针的位置进行互换,互换之后,左指针自增一,右指针是每次遍历都会自增一的,所以会一直增加,而左指针只有在数字是非0的情况下才会自增一。
这样操作的结果会将所有非0的数字依据左指针自增的位置依次存放,不对0进行处理,再举个例子:
[0, 1, 0, 3, 12]
下面一步步操作:
| 左指针 | 右指针 | 数组 |
|---|---|---|
| 0 | 0 | [0, 1, 0, 3, 12] |
| 1 | 1 | [1, 0, 0, 3, 12] |
| 1 | 2 | [1, 0, 0, 3, 12] |
| 2 | 3 | [1, 3, 0, 0, 12] |
| 3 | 4 | [1, 3, 12, 0, 0] |
这样值等右指针走到头的时候,非0数字都跑到了数组左侧,0也就自然的跑到右边了。
自己的答案(splice+push)
最容易想到的方法👇:
var moveZeroes = function(nums) {
let left = 0
let end = nums.length
while (left < end) {
if (!nums[left]) {
nums.splice(left, 1)
nums.push(0)
end--
} else {
left++
}
}
return nums
};
利用end来统计0出现的次数,这样可以避免无效的遍历。
自己的答案(双while)
var moveZeroes = function(nums) {
const len = nums.length
let right = len - 1
let count = 0
while (right > -1) {
while (nums[right] === 0) {
for (let i = right; i < len - count - 1; i++) {
[nums[i], nums[i + 1]] = [nums[i + 1], nums[i]]
}
count++
right--
}
right--
}
return nums
};
这就是解释种说到的第二种方法,可以看到内部while种进行了数组的批量替换操作,虽然逻辑很正,但是做了很多无效操作,尤其是在数组头部出现0的情况下,性能损耗较大。
自己的答案(双指针)
var moveZeroes = function(nums) {
const len = nums.length
let left = 0, right = 0
while (right < len) {
if (nums[right]) {
[nums[left], nums[right]] = [nums[right], nums[left]]
left++
}
right++
}
return nums
};
双指针的做法大量减少了没必要的替换次数,而且这里也不一定用while,用for循环也是一样的,为了更好的理解双指针嘛。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇