题目:https://leetcode-cn.com/problems/move-zeroes/

题目有两个要求:
- 将所有 0 移到数组的末尾。
- 所有非零的数字保持顺序。
方法一:双指针
思路其实就是找非零数字需要插入的索引位置。
假设数组为 [0, 0, 2, 3] ,在遍历的时候,我们找到0的索引位置为0,此时就需要记住这个索引位置就是下一个非零数字需要插入的地方。下一个非零数字“2”的索引为2,经值交换后,数组变为 [2, 0, 0, 3] 。
此时数组继续遍历,得到 0 的索引位置为 1,此时非零数字要插入的索引位置就为1了。而下一个非零数字“3”的索引为 3,经值交换后数组变为 [2, 3, 0, 0] 。
而下面的题解,就是上面思路的实现:
/**Do not return anything, modify nums in-place instead.*/function moveZeroes(nums: number[]): void {// 记录非零数字需要插入的索引位置let insertIndex = 0;for (let i = 0; i < nums.length; i++) {// 找出非零元素,并将非零元素插入到 insertIndex 位置// 仅需找出 nums[i] !== 0 的情况即可,nums[i] === 0 的情况下交换值没有任何意义,因为值都是0if (nums[i] !== 0) {if (i !== insertIndex) {nums[insertIndex] = nums[i]nums[i] = 0}// 下面一步之所以注释,是因为这一步是多余的,因为如果索引一致的话,表明它们的值都是一致的,// 因此完全没有必要在进行值交换的操作// if (i === insertIndex) {// const tmp = nums[insertIndex]// nums[insertIndex] = nums[i]// nums[i] = tmp// }insertIndex++}}};
复杂度分析:
- 时间复杂度:
- 空间复杂度:
方法二:使用 JavaScript 原生 Api
直接使用 JavaScript 是最为简洁易懂的,直接使用 splice() 来删除数组中的 0 元素,然后再向数组末尾添加 0。具体解法如下:
/**Do not return anything, modify nums in-place instead.*/function moveZeroes(nums: number[]): void {for (let i = 0, count = 0; count < nums.length; count++) {if (nums[i] === 0){nums.splice(i, 1)nums.push(0)} else {i++}}};
复杂度分析:
- 时间复杂度为
- 空间复杂度为
正常来说,是直接使用自增的 i 来读取数组元素,但是由于使用 spilice api 后,已经更改了数组元素的索引了,如果此时仍然继续使用自增的 i,此时其实是会跳过一些元素的。以下面的代码为例可以看出问题所在。
const arr = [0, 1, 2, 0, 3]for (let i = 0; i < arr.length; i++) {if (arr[i] === 0) {arr.splice(i, 1)arr.push(0)}}// 第一次循环结果:// arr [ 0, 1, 2, 0, 3 ]// arr[i] 0// 第二次循环结果:// arr [ 1, 2, 0, 3, 0 ]// arr[i] 2// 第三次循环结果:// arr [ 1, 2, 0, 3, 0 ]// arr[i] 0// 第四次循环结果:// arr [ 1, 2, 3, 0, 0 ]// arr[i] 0// 第五次循环结果:// arr [ 1, 2, 3, 0, 0 ]// arr[i] 0
我们从结果可以看出, arr[i] 所打印的结果是有遗漏的。因此在解法中,使用了 count 变量来限制循环次数与数组的元素个数一致。
变量 i 则主要用于读取数组的元素,在对数组进行了操作的时 i 的数值是不会做增加的操作,因为此时索引 i 的元素已经变为原来 i+1 的元素,所以如果 i 在加 1 的话,其实就会跳过了一个元素。
方法三:找出所有非零元素与零元素
思路:找出所有的非零元素和零元素。
/**Do not return anything, modify nums in-place instead.*/function moveZeroes(nums: number[]): void {// 获取 0 的个数let zeroArr: number[] = []nums.forEach(num => {if (num === 0) {zeroArr.push(0)}})// 获取所有非 0 元素const noneZeroNums: number[] = []nums.forEach(num => {if (num !== 0) {noneZeroNums.push(num)}})// 合并结果const combineArr = noneZeroNums.concat(zeroArr)// 替换合并结果for(let i = 0; i < nums.length; i++) {nums[i] = combineArr[i]}};
复杂度分析:
- 时间复杂度:
- 空间复杂度:
方法四:sort 排序
/**Do not return anything, modify nums in-place instead.*/function moveZeroes(nums: number[]): void {nums.sort((a, b) => b === 0 ? -1 : 0)};
sort 的结果受回调函数返回值的影响:
- 当回调函数返回的值小于0时,a 的索引比 b 的索引要小。
- 当回调函数返回的值等于0时,a 的索引和 b 的索引不会有任何变化。
- 当回调函数返回的值大于0是,a 的索引比 b 的索引要大。
需要注意的是,如果数组为 [1, 2] ,那么 a 的值为 2,而 b 的值为1。也就是说,a、b 的值的顺序与数组的顺序是相反的。我们只要判断 b 是否为0即可,当b为0,则将它置后。
