快慢指针-从leetcode题目说起

我是天空里的一片云,偶尔投影在你的波心。 --偶然

前言

先来看一道算法题:27.移出元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例

例如:

输入:nums = [3,2,2,3], val = 3 输出:2, nums = [2,2] ,给定nums = [0,1,2,2,3,0,4,2], val = 2 输出:5, nums = [0,1,4,0,3]

想必各位第一时间想到的是JavaScript的splice函数,使用 for 循环,依次遍历数组,找到等于 val 的元素并删除,

js 复制代码
/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
const removeElement = function (nums, val) {
    let len = nums.length;
    for (let i = 0; i < len; i++) {
        if (nums[i] === val) {
            nums.splice(i, 1);
            len--;
            i--;
        }
    }
    return len;
};

这样看,这道题这道题很简单是不是?直接使用了 JavaScript 的 API,简单直接

好了,现在恭喜你,喜提一个称号,API 调用工程师,请戴好帽子🎩(可个玩笑

那么我们再看看这样的执行速度呢?

Oops,好像并不理想,内存占用只高于27.04%,

实际上,我们这样做,就失去了这道题的意义,这道题考察的是对于数组的理解:

从数组说起

我们都知道,数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。数组是一片连续的内存空间(这里只讨论狭义的数组,快数组慢数组不在讨论范围),而且数组没有删除操作,如果想要删除一个元素,在数组内部是比较复杂的操作,需要将即将删除的下表的位置依次覆盖,

比如,数组 a,b,c 对应的下标依次是 1、2、3,如果我们要删除元素a,那么就需要将 a 后面的元素依次前移,由 a、b、c,对应下表 1、2、3,变为 b、c,对应下表 1、2、3,

了解了数组的删除操作,那么在这里,我们是不是可以复原一下这个思路呢?

暴力算法

知道了解题思路,我们是不是就可以开始着手解决问题了。

首先想到的是双层 for 循环的暴力解法,第一层 for 是找出需要删除的元素,第二层 for 是依次将后面的元素向前移动,覆盖需要删除的位置。

js 复制代码
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
/**
 * 
 * @param nums
 * @param val
 * @returns {*}
 */
function removeElementFor(nums, val) {
    let size = nums.length;
    for (let i = 0; i < size; i++) {
        if (nums[i] === val) { // 发现需要移除的元素,就将数组集体向前移动一位
            for (let j = i + 1; j < size; j++) {
                nums[j - 1] = nums[j];
            }
            i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
            size--; // 此时数组的大小-1
        }
    }
    return size;
}

这种算法并不需要太深入的思考

双指针

那么我们可否优化一下暴力解法,记录下当前需要删除的位置?

实际上,我们可以用两个指针,分别为快慢指针,快指针用于遍历所有元素,慢指针用于记录下当前除去需要删除的元素的下标,

这里借用卡尔老师代码随想录的图:代码随想录,卡尔老师的算法讲解的不错,大家可以去瞅瞅

上代码:

js 复制代码
/**
 * @param {number[]} nums
 * @param val
 * @returns {number}
 */
const removeElement1 = (nums, val) => {
    let slow=0;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] !== val) {
            nums[slow++] = nums[i];
        }
    }
    return slow;
}

看一下执行结果:

可以看到,使用快慢指针后,代码执行时间以及代码执行的内存都有了巨大的提升。

总结

在了解数组删除原理的基础上,我们可以使用双指针的思路来解决。顺便说一下,对于算法题,我们尽量不要使用库函数,因为算法题本身就是对于我们思维的一个学习历练过程,直接使用库函数,一行代码把原来需要解决的问题给搞定了,也就失去了是考的过程了。

链接

更多快慢指针题目

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

相关推荐
Captain823Jack31 分钟前
nlp新词发现——浅析 TF·IDF
人工智能·python·深度学习·神经网络·算法·自然语言处理
Captain823Jack1 小时前
w04_nlp大模型训练·中文分词
人工智能·python·深度学习·神经网络·算法·自然语言处理·中文分词
是小胡嘛2 小时前
数据结构之旅:红黑树如何驱动 Set 和 Map
数据结构·算法
m0_748255022 小时前
前端常用算法集合
前端·算法
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
呆呆的猫2 小时前
【LeetCode】227、基本计算器 II
算法·leetcode·职场和发展
Tisfy2 小时前
LeetCode 1705.吃苹果的最大数目:贪心(优先队列) - 清晰题解
算法·leetcode·优先队列·贪心·
余额不足121382 小时前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
火星机器人life5 小时前
基于ceres优化的3d激光雷达开源算法
算法·3d
虽千万人 吾往矣5 小时前
golang LeetCode 热题 100(动态规划)-更新中
算法·leetcode·动态规划