一道题目让你彻底搞懂快慢指针

274 阅读3分钟

引言

大晚上睡不着觉,刷了道力扣上的算法题(力扣第27题),觉得挺有意思的,故分享给大家。请看题目:

image.png

许多小伙伴一看这道题,心想这直接用两个for 循环不就搞完了嘛,这有什么可以讲的,但请别着急划走,给我几分钟让你重新认识这道题目。看这道题目之前,请容许我先稍微讲一下时间复杂度。

一.时间复杂度

  • 时间复杂度通常用大O表示法来表示,它表示在输入规模足够大时,算法运行时间的增长趋势。

  • T(n)用来表示代码的执行次数,顾名思义,T就是Time 次数的缩写。

  • 时间复杂度和T(n)间的关系:假设 T(n) 是算法在输入规模为 n 时所需的运行时间,那么时间复杂度O(f(n)) 表示存在常数 c>0 和 n0≥0,使得对于所有的 n≥n0,都有 T(n)≤c⋅f(n),

许多同学看这一段看的头都要晕了不知道这到底是什么东西,下面让我用一段代码让你更好的理解它,请看下面的代码:

function traverse(arr) {
  var len = arr.length; // 1
  for (var i = 0; i < len; i++) { // 1 + n + 1 + n
    console.log(arr[i]); // n
  }
}

这段代码的执行次数T(n)= 1 + 1 + n + 1 + n + n = 3n + 3 ,而由边界理论,当输入规模无限大,它的最高系数是主导项,所以,我们可以忽略T(n)的系数,用O(n)来表示它的时间复杂度

二.暴力解法

这可能是许多小白同学最容易想到的一种方法,当然了这种方法在力扣上也是可以通过没有任何问题的。

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function (nums, val) {
    let count = 0  // count用来记录不等于 val 的元素数量
    const arr = [] // 创建一个临时数组来存储不等于 val 的元素

    for (let i = 0; i < nums.length; i++) {
        if (nums[i] !== val) {
            arr.push(nums[i])
            count++
        }
    }
    for (let i = 0; i < arr.length; i++) {
        nums[i] = arr[i] //把临时数组的值复制回原数组
    }
    return count
};

但由于它用了两个for 循环,时间复杂度虽然也为O(n),并不是最优解,因为他创建了一个额外的的数组,增加了空间开销。作为顶级大佬的大家肯定是要追求最优啦,那就让我来把它的最优解,用快慢指针的做法来讲给你听啊

三.快慢指针

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 这样我们就能提高代码的运行效率,节省时间。

定义快慢指针 1

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

最后把快指针里的值复制到慢指针里,返回慢指针,这道题就做完了。

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function (nums, val) {
    let s = 0 //定义一个慢指针
    for (let f = 0; f < nums.length; f++) { //定义一个快指针
        if (nums[f] != val) {
            nums[s++] = nums[f]  // 将快指针的值复制给慢指针
        }
    }
    return s
};

许多同学可能最大的问题就是nums[s++]= nums[f]这一行代码了,根本不知道nums[s++]哪来的,其实这一行是由

nums[s]= nums[f]
s++

这两行代码结合起来的,当碰到val时,慢指针其实是不会动的,只要到下一个非val的值时,慢指针才会往后走,所以需要s++,而为了代码的简洁,许多人也就把这两行代码缩成一行了。

th.jpg

大晚上码字不容易,点个赞再走吧~~~