本篇博客会讲解力扣“283. 移动零”的解题思路,这是题目链接。
思路1
这道题目很有意思。虽然是简单题,其蕴含的玄机还是很多的。正常来讲,这种题目一般都会原地操作(不开辟额外的数组,空间复杂度是O(1)),并且通过一次遍历来完成,这样效率是最高的。我拿到这道题的第一反应是,可以把前面的0都删了,然后后面再补0。具体来说:
- 使用2个下标,分别是fast和slow。
- fast负责在前面跑,寻找非0数,丢给slow,slow再往后走。
一开始slow是0,显然左边没有0。接着,slow一般不动,除非fast丢了个非0数给slow,slow才会往后走,此时slow左边也没有0。直到fast把数组遍历完,slow的左边都不会有0,且slow的左边就是原来数组的非0数,并且保持原来的顺序。最后从slow的位置开始,到数组末尾的数据全部都改成0即可。
void moveZeroes(int* nums, int numsSize) {
int fast = 0;
int slow = 0;
// 把非0数搬运到左边,删除0
for (fast = 0; fast < numsSize; ++fast)
{
if (nums[fast])
{
nums[slow++] = nums[fast];
}
}
// 后面补0
for (; slow < numsSize; ++slow)
{
nums[slow] = 0;
}
}
思路2
思路1运用了较为取巧的思路,既然要把0扔到最后面,不如把非0数扔到前面(即删除0),后面再补0,这是一种覆盖的思路。
我们还可以从“交换”的角度来思考这个问题。还是双指针,分别是left和right。我们规定,left的左边没有0,left到right之间都是0。有没有发现,这和快速排序的单趟排序非常的像?在快速排序中,我们会选择一个key,在单趟排序后,保证key左边的值比key小,右边的值比key大,一个经典的思路就是,使用双指针left和right,每次交换后,都保证left左边的值比a[left]小,右边的值比a[left]大。当然,如果你没有联想到快速排序,也是可以解决这道题目的。我们可以这样思考:
- 一开始left和right都是0,满足left的左边没有0的条件。
- 接着,若right的值非0,则交换left和right,再让left和right向右移动,此时left的左边依然没有0。
- 如果right的值一直都非0,就会一直进行第2点的操作,从而left和right不会错开。
- 若right的值是0,则left不动,right向右移动,依然满足left的左边没有0的条件。
- right会遍历完整个数组,并且只遍历一遍,所以无论如何都要向右移动。
void moveZeroes(int* nums, int numsSize){
int left = 0;
int right = 0;
while (right < numsSize)
{
// 保证left左边全是非0数
if (nums[right])
{
// 交换左右下标
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
++left;
}
// 不管nums[right]是不是0,right都要向右移动
++right;
}
}
总结
移动0有两种思路:
- 把0直接覆盖删除,最后在数组的末尾补0。
- 使用单趟快排的思路,不断交换,把非0数换到左边,0换到右边。
感谢大家的阅读!