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();
};
解法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;
}
};
解法2 替换数据
由解法1,其实用了2个循环,如果对调下left,和right的值,是否就不需要循环2次了。js有提供一个对调的函数,如下所示:
故代码可以升级为下面这种方式:
- 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++;
}
};
总结
虽然 解法2 比 解法1 少一个循环,单实际上 内存消耗 和 执行时间 都比解法1 要大一点,因为对调函数 [a,b] => [b,a] 的底层实现方式如下:
故解法1 是最优解。