给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
相当于把在不改变所有非零元素的相对顺序的前提下将其移动到数组的最前端。最直观的思路就是遍历数组,遇到非零元素就往前扔。那么新问题来了:扔到哪里?所以就需要再声明一个变量去标记应该扔到哪里。这很像双指针,所以不妨右指针一直往后找非零元素,找到了,就把非零元素扔到左指针所在的位置。一旦左指针所在的位置为非零元素,左指针就要往后移动一位,否则就是占着茅坑不拉屎:因为左指针的用处本来就是标记下一个非零元素应该放到哪里,现在它的位置已经被占了,就应该往后挪。
粗俗地说,右指针是一位盲人,非零元素好比右指针拉的屎,左指针是它的导盲犬,它告诉右指针应该往哪里拉屎。如果一个坑已经被屎填满了,那导盲犬应该移动到下一个空的坑位,这样右指针才能知道它下一次应该把屎拉在哪里。
需要注意的是,覆盖和交换略有差别。如果是覆盖,那么最终就要再从左指针left的位置开始,往后面遍历直到数组末尾,将这中间遇到的所有元素都赋值为0.因为没有交换。之所以是从left开始,不是left+1或left-1,是因为left本来就是标记下一个非零元素应该扔到哪里,而最后非零元素一定都被扔到left前面了,所以left开始后面的茅坑必须不能被屎填满。如果遇到了全部为0的数组,那么对于覆盖就要遍历两次数组。但交换的话就不需要考虑这个。
Java:
class Solution {
public void moveZeroes(int[] nums) {
//右指针一直往后移,遇到非零元素,就往前扔
//具体扔到哪里,由左指针控制。扔到左指针所在的位置
int left=0,right=0,n=nums.length;
while(right<n) {
if(nums[right] != 0) {
//交换
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
}
right++;
}
//如果是覆盖,那么对于一个全部为0的数组,要遍历两次
}
}
Go:
func moveZeroes(nums []int) {
left,right,n := 0,0,len(nums)
for right < n {
if nums[right] != 0 {
temp := nums[left]
nums[left] = nums[right]
nums[right] = temp
left++
}
right++
}
}
C++:
class Solution
{
public:
void moveZeroes(vector<int>& nums)
{
int n = nums.size();
int left = 0, right = 0;
while (right < n)
{
if (nums[right])
{
swap(nums[left], nums[right]);
left++;
}
right++;
}
}
};