题目
Given an integer array nums, move all 0’s to the end of it while maintaining the relative order of the non-zero elements.
Note that you must do this in-place without making a copy of the array.
思路
使用双指针,pz指向第一个0开始的位置,pn指向第一个非0开始的位置,每一次操作交换两指针指向的数值。用该思路模拟一遍输入示例:
0 1 0 3 12
pz=0, pn=1
1 0 0 3 12
pz=1, pn=3
1 3 0 0 12
pz=2, pn=4
1 3 12 0 0
因此大致可以判断使用一个while循环来做,每一次循环先找到第一个0的位置,再找到第一个非0的位置。
循环的结束条件 理所当然地可以想到,当两个指针都到数组末尾位置n-1的时候,应该退出循环。虽然这种做法称不上优雅,但是至少可以作为暂时的思路。
int pz = 0, pn = 0;
while(true){
while(nums[pz] && pz < len - 1) pz ++; //找第一个0的位置
while(!nums[pn] && pn < len - 1) pn ++; //找第一个非0的位置
if(pz == len - 1) break; //0指向末尾,无需进行任何交换操作,直接结束
/*执行交换操作*/
if(pn == len - 1) break; //非0指向末尾,并且已经执行过交换操作,结束
}
预处理 考虑到以下一种情况:
1 0 1
当我们使用上述思路进行操作的时候,会意外地发现,最后输出的结果会是:
0 1 1
这是因为在我们的循环当中,总是第一个0和第一个非0进行交换,而无论谁的顺序在前都会执行该操作。因此,想到在循环之前先行找到第一个0的位置,而后直接将pn(非0指针)拨到0的位置上,从而确保非0指针一定是在0指针之后的。
时间复杂度 该思路的时间复杂度为0-2n. 当数组全部为0时,无需任何操作;当数组第一位为0,后续都非0时,pz从0遍历到n,pn从0遍历到n,总共需要执行2n次操作。
C++代码
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int pz = 0;
int len = nums.size();
while(nums[pz] && pz < len - 1) pz++;
int pn = pz;
while(true){
while(nums[pz] && pz < len - 1) pz++;
while(!nums[pn] && pn < len - 1) pn++;
if(pz == len - 1) break;
int temp = nums[pz];
nums[pz] = nums[pn];
nums[pn] = temp;
if(pn == len - 1) break;
}
}
};
官方题解
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
left: 左指针左边均为非零数 -> 指向第一个0
right: 右指针左边直到左指针处均为零 -> 指向第一个非0
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size(), left = 0, right = 0;
while (right < n) {
if (nums[right]) {
swap(nums[left], nums[right]);
left++;
}
right++;
}
}
};
反思
官方题解提供的一种思路,即将待处理数组一分为二,处理好的序列&待处理序列。虽然left也是指向第一个0的位置,也就是pz,但是其是作为处理好的序列的末尾来看待的。也就是说,处理好的序列包括[num0, num1, num2, …, 0]. 待处理序列包括[num_r, …]. 该思路的代码写的非常优雅,while的结束条件为右指针不超出数组,处理1 0 1的时候,它直接解决了r在l左侧的问题。比while里面两个while找位置要优雅很多很多,非常值得学习。
而我的思路仅仅是看点对点进行交换,因此需要考虑很多种情况,造成break的条件不清晰,结束条件比较的长度是多少、在哪个位置进行break等等都需要考虑。同时,我的思路当中的处理好的序列其实是不包含末尾0的。
229

被折叠的 条评论
为什么被折叠?



