LeetCode 283——Move Zeroes

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Example:

Input:[0,1,0,3,12]

Output:[1,3,12,0,0]

Note:

  1. You must do this in-place without making a copy of the array.
  2. Minimize the total number of operations.

 


 

官方的solution中称这种问题为 "Array Transformation",我们暂且称他叫数组转换问题吧

做了一些题发现了一个通性就是:题目描述比较简洁;但是时间复杂度、空间复杂度上要求的要严格的多。

回到这个题目上,读完题目我就有了两个纠结点:

抛去对时间的考虑,直接对数组进行嵌套循环式的平移即可。

抛去对空间的考虑,直接对将数组的非零元素和零元素分开添加到一个新的数组中就可以了。

那有没有两全其美的办法?

当然是有的,在后边一种假设中,我们其实将整个流程分为了两步:

  1. 将所有的非零元素添加到数组中
  2. 将所有的零元素添加到数组中

这样想下去,就是要在数组内先把非零元素提到最前边,后边的零填充就非常简单了,使用时间复杂度为O(N^{2})的方法就和嵌套循环式的平移没有差别了,所以时间复杂度一定要更小,这时 "two pointer" 的思想就登场了,它恰恰能解决缺少一个动态记录位置的索引,而又怕时间复杂度过高的问题。

用一个 pointer 来记录最前边的零元素,另一个 pointer 来记录其后的第一个非零元素,步骤如下:

  • 首先我们将用来记录零元素的索引命名为“firstZero”,另一个性质有点像遍历时使用的游标的感觉,命名为“i”
  • firstZero 和 i 的初始值均为0,进行循环
  • 每次检测nums[i] != 0 是否成立
  • 如果成立,则令 nums[firstZero++] = nums[i],就是将最前边的零元素用其后的第一个非零元素覆盖,在第一次循环时,若前几个数字均为非零的,则firstZero和 i 的值一直保持同步;如果不成立,则没有操作。
  • 每次循环结束执行 ++i,并检测 i < nums.lenth 是否成立

此时得到的firstZero的值后边全部填充零即可。现在题目的要求都能满足,而且有了一个比较低的时间复杂度。

但是,这道题仍然有改进的余地,每次都要在后边填充零其实是浪费的,因为用给出的例子一步一步操作之后可以看出,最后的零被覆盖掉了,我们完全可以通过交换两个元素的位置来达到同样的目的,避免时间的浪费。

 

上边的解答,很大程度参考了官方的解答,而我自己在解题的时候虽然也想到了 "two pointer"思想,但是显然做法不够简洁和成熟,说明对这个思想的掌握程度还是不高,需要勤加练习。

数组转换问题很多都会涉及到 "two pointer" 思想,所以在没有头绪的时候,不妨朝这个角度去想一想,或许写出来的代码看起来不够流畅,但是能够将题目解决也是不错的。

 

下边会放有最佳题解和我的题解,均是正确的,但是官方的解法稍微做了一些修改,原来数组交换使用的是swap函数,但是我觉得,需要交换的其中之一已经明确知道是零了,所以不妨使用直接赋值的方式更能提升速度。(这句话被划掉了,我也没做修改,可以思考一下修改之后会发生什么情况)

 


 

最佳代码:

class Solution {
    public void moveZeroes(int[] nums) {
        int numLen = nums.length;
        int firstZero = 0;
        for (int i = 0; i < numLen; ++i) {
            if (nums[i] != 0) {
                int tmp = nums[i];
                nums[i] = nums[firstZero];
                nums[firstZero++] = tmp;
            }
        }
    }
}

 

我的解法: 

class Solution {
    public void moveZeroes(int[] nums) {
        int numLen = nums.length;

        int firstZero;
        for (firstZero = 0; firstZero < numLen; ++firstZero) {
            if (nums[firstZero] == 0) {
                break;
            }
        }
        int i = firstZero + 1;
        while (firstZero < numLen && i < numLen) {
            if (nums[firstZero] == 0 && nums[i] != 0) {
                nums[firstZero] = nums[i];
                nums[i] = 0;
            }

            if (nums[i] == 0) {
                ++i;
            }
            if (nums[firstZero] != 0) {
                ++firstZero;
            }
        }
    }
}

 


 如有错误,欢迎指摘。也欢迎通过左上角的“向TA提问”按钮问我问题,我将竭力解答你的疑惑。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值