移动零(283)

83 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

题目描述


给定一个数组 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

进阶:你能尽量减少完成的操作次数吗?


错误解法

刚看到题目,我在想啥leetcode时候有这么简单的题目,reverse()不仅能解决,运行后发现不行。好尴尬 哈哈哈。

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function (nums) { 
    nums = nums.reverse();
};

image.png

解法1 双指针

言归正传,看清题目后,我发现可以用双指针解决。

  • 设置 left, right 2个指针,从头开始遍历
  • left 指针循环遍历nums的每一个元素
  • right 指针记录非 0 的索引值
  • 非0 则把 nums[left] 的值复制给 nums[right],通常情况下 right<=left

时间复杂度: O(n)

空间复杂度: O(1)

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    //left 指针循环遍历nums的每一个元素 
    //right 指针记录非 0 的个数
    let left = 0,
    right = 0;
    while(left < nums.length){
        // 不等于0 数字前移动
        if(nums[left] != 0){
            nums[right] = nums[left];
            right++;
        }
        left++;
    }

    //由循环1知 前right个为非0的数字,故 [right,nums.length-1] 为 0
    for(var i = right; i < nums.length; i++ ){
        nums[i] = 0;
    }
};

image.png

解法2 替换数据

由解法1,其实用了2个循环,如果对调下left,和right的值,是否就不需要循环2次了。js有提供一个对调的函数,如下所示:

image.png

故代码可以升级为下面这种方式:

  • left 指针循环遍历nums的每一个元素
  • right 指针记录非 0 的索引值
  • 当第left个数不为0,与第right替换数字
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let left = 0,right = 0;
    while(left < nums.length){
        if(nums[left] != 0){
            [nums[right],nums[left]] = [nums[left],nums[right]];
            right++
        }
        left++;
    }
};

image.png

总结

虽然 解法2 比 解法1 少一个循环,单实际上 内存消耗 和 执行时间 都比解法1 要大一点,因为对调函数 [a,b] => [b,a] 的底层实现方式如下:

image.png

故解法1 是最优解。