leetcode283题(移动零),JS"快慢指针"解法

551 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

JS 中是没有指针(pointer)的概念的,只有引用(reference),一个简单的判断依据就是:C语言中指针是可以有 ++ 操作的,但 JS 办不到。JS 引用与指针类似的地方在于他可以对同一个地址的值进行操作,我们可以简单地认为 JS 变量储存了一个对象的地址。本文以双指针(快慢指针)概念分析解题思路,JS代码为题解

leetcode283题(移动零)

题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

必须在原数组上操作,不能拷贝额外的数组。尽量减少操作次数。

思路及代码实现

方案1

  • 在不考虑说明的要求下,可以借用新数组的方法。先遍历原数组,判断元素如果不为0,则依次把值赋值到新的数组中,一次遍历后,把新数组剩余长度元素都赋值为0
  • 此时时间复杂度和空间复杂度都为O(n)
        let moveZeroes = (nums) => {
            let newArr = []
            for(let i = 0;i < nums.length; i++) {
                if (nums[i] !== 0) {
                    newArr.push(nums[i])
                }
            }
            let length = newArr.length - 1
            for(let i = length; index < nums.length - length; i++) {
                newArr[i] = 0
            }
            return newArr
        }
    

方案2

  • 在符合说明的要求下,使用双指针(快慢指针)解法:快针不断向右移动,每次快指针指向非零数,则将快慢指针对应的数交换,同时慢指针右移。最后慢针左边均为非零数,快针左边直到慢指针处均为零。因此每次交换,都是将慢指针的零与快指针的非零数交换,且非零数的相对顺序并未改变。

  • 此时时间复杂度为O(n),空间复杂度为O(1)

        // 每一次遍历会执行 fast++,而慢指针只有在元素不为0时才执行 slow++,因此形成快慢指针
        var moveZeroes = function(nums) {
            let slow = 0 // 定义慢指针
            for(let fast = 0;fast < nums.length; fast++) {
                if(nums[fast] !== 0) {
                    if (fast !== slow) { // 快慢指针如果在同一位置,没有必要操作交换(减少操作次数)
                        let temp = nums[slow]
                        nums[slow] = nums[fast]
                        nums[fast] = temp
                    }
                    slow++
                }
            }
        }
    

    发难2.png

方案3

  • 方案2在交换的过程中,slow或者fast所在元素都被访问2次;如果从尽量减少访问的次数角度看,可以换成直接赋值的思路代替原来交换的方式。最后将slowfast之间的元素赋值为0

  • 此时时间复杂度为O(n),空间复杂度为O(1)

        
            var moveZeroes = function(nums) {
                let slow = 0
                for(let fast = 0;fast < nums.length; fast++) {
                    if(nums[fast] !== 0) {
                        if (fast !== slow) {
                            nums[slow] = nums[fast]
                        }
                        slow++
                    }
                }
                // 将 slow 到 fast 之间的元素赋值为0
                for(let i = slow; slow < nums.length; slow++) {
                    nums[slow] = 0
                }
            }
    

    image.png

总结

  • 方案2、3差异不大,只是从不同角度考虑解决思路
  • 在解决基于数组的算法中,通常一个指针用于数组遍历,另一个指针用于记录信息。这种双指针(快慢指针)的思路在某些场景下可以降低空间复杂度,提高性能