题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
我的解法
class Solution {
public void moveZeroes(int[] nums) {
int temp;
//冒泡排序不改变数组元素相对顺序
for(int i=nums.length-1;i>=0;i--){
if(nums[i]==0){
//遇到0,直接让其沉底
for(int j=i;j<nums.length-1;j++){
temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
}
我的思路:遇到0则让0沉底。时间复杂度太高了。
这还有个小心机:如果外循环从小到大的话,那不行,都不知道0后面的数是啥,可能还是0与0交换。
这里外循环是从大到小,就保证了每次循环都是正确的。
官方解法
方法一
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
注意到以下性质:
左指针左边均为非零数;
右指针左边直到左指针处均为零。
因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。
遇到0则右指针右移,左指针不动,
遇到非0的数,交换两指针的数,然后两个指针都右移,
如果一直没有0,那么左右指针一直指向同一个数,自己和自己交换
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
方法二:
这里参考了快速排序的思想,快速排序首先要确定一个待分割的元素做中间点x,
然后把所有小于等于x的元素放到x的左边,大于x的元素放到其右边。
这里我们可以用0当做这个中间点,把不等于0(注意题目没说不能有负数)的放到中间点的左边,等于0的放到其右边。
这的中间点就是0本身,所以实现起来比快速排序简单很多,我们使用两个指针i和j,只要nums[i]!=0,我们就交换nums[i]和nums[j]
class Solution {
public void moveZeroes(int[] nums) {
if(nums==null) {
return;
}//考虑空指针
//两个指针i和j
int j = 0;
for(int i=0;i<nums.length;i++) {
//当前元素!=0,就把其交换到左边,等于0的交换到右边
if(nums[i]!=0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j++] = tmp;
}
}
}
}
细节处理
/*已知是0,不交换,直接赋值*/
public void moveZeroes(int[] nums) {
int indexNow = 0; //已处理好的序列的尾部
int indexNum = 0; //待处理序列的头部
int m = nums.length;
//非0则两个指针都++,0则移动待处理指针即可
while(indexNum<m){
if(nums[indexNum] != 0) {
nums[indexNow++] = nums[indexNum];
}
++indexNum;
}
for(int i = indexNow; i < m; i++){
nums[i] = 0;
}
}
/*将右指针变成for循环*/
public void moveZeroes(int[] nums) {
if(nums==null) {
return;
}
//两个指针i和j
int j = 0;
for(int i=0;i<nums.length;i++) {
//当前元素!=0,就把其交换到左边,等于0的交换到右边
if(nums[i]!=0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j++] = tmp;
}
}
}
剖析
这两种方法都算是双指针吧,没什么本质差别。
一个指针记录排好序的,一个记录未排好序的,数据从一端到另一端。
这种解决问题的思路值得借鉴。