壹 ❀ 引

整理下今天做的算法题,题目难度不高,但在优化角度也是费了一些功夫。题目来自219. 存在重复元素 II,问题描述如下:

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

题目意思其实很简单,看一个数组中是否有两个元素相等,且后者索引减去前者的差集,要小于等于数值k。注意不是等于k,我第一次提交就是看错了直接给挂了,接下来我们来说说怎么做。

贰 ❀ 暴力解法

我首先想到的自然是for循环遍历嵌套,用两个索引分别表示数组中一前一后的元素,如果两个元素相等,且后者索引减去前者索引的值<=k,则返回true即可:

/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var containsNearbyDuplicate = function (nums, k) {
let ans = false
for (let i = 0; i < nums.length; i++) {
// 注意这里j从i+1开始
for (let j = i + 1; j < nums.length; j++) {
// 满足两数相等,且索引差不大于k即可
if (nums[i] === nums[j] && j - i <= k) {
return true;
};
};
};
return ans;
};

叁 ❀ 震惊的负向优化

我在上篇文章中提到,如果能将时间复杂度从O(n²)降到O(n),那将是大大的优化,尽管上述代码遍历也并未达到n²,但我们还是可以试试。

在阅读了官方推荐的哈希表做法后,我实现了如下代码:

/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var containsNearbyDuplicate = function (nums, k) {
// 我们始终维护一个大小为k的哈希表
let hash = [];
for (let i = 0; i < nums.length; i++) {
// 判断有没有当前元素,开始为空肯定没有
if (hash.includes(nums[i])) {
return true;
};
// 将当前元素加入哈希表中
hash.push(nums[i]);
// 前面说了,哈希表大小始终为k,超过了,我们就删除最旧的数据
if (hash.length > k) {
hash.shift();
};
};
return false;
};

实现不难理解,我们始终维护一个大小为k的哈希表,并依次将元素加入表内,由于一开始为空,自然是加入第一个。从第二个开始判断有没有,如果有自然返回true。反之没有继续加入表内。但需要注意的是,我们的表的大小是k,一旦超过我们就得删除掉最旧的数据。

思路清晰,然后我提交了代码,一看执行时间,我人都傻了,出于怀疑我又点了一次提交(2000ms与1780ms)。

从表面上看,暴力解法用了两次循环嵌套,而哈希表做法只用了一次。其实从内部实现来看,循环嵌套做的事情要简单的多。

我们知道数组是呈线性排列的一种数据结构,当我们通过索引直接访问某条数据时,它的时间复杂度为O(1),而做查找操作就不同了,由于没有提供标识,我们只能通过线性查找一个接一个进行对比,看看当前是不是我们想要的。看看hash.includes(nums[i])这行代码,你是否意识到了什么呢?

除此之外,我们在下方代码还做了shift操作,也就是数组删除,由于数组是连续的,出现空缺就得填补,像这样:

所以整体上来看,双循环嵌套执行次数看着虽然多,但站在时间复杂度角度来说,每一次操作单元耗时微乎其微,都是根据索引直接找到对应元素对比。而后一种实现每次遍历做的事情就非常耗时了。

肆 ❀ 使用ES6 set结构

奇妙的是,同样的思路,我们将数组换成set结构,速度就快了很多,只用了88ms,这里引用灵魂画手的实现:

/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var containsNearbyDuplicate = function(nums, k) {
// 创建哈希表
const set = new Set();
for(let i = 0; i < nums.length; i++) {
// 判断有没有
if(set.has(nums[i])) {
return true;
};
set.add(nums[i]);
if(set.size > k) {
// 删除最旧数据
set.delete(nums[i - k]);
};
};
return false;
};

可以看到只是单纯换了数据结构,执行用时质的提升,这里我不禁对于数据结构差异产生了兴趣,在知乎javascript 里的Set.has和Array.includes谁的效率更高?提问中,有用户做过测试,在大量数据下,set要远高于数组。但本质原因我一时无法考证了,只能在心里埋下一枚问题种子,待日后算法与数据结构的不算学习,希望能给自己一个答案。

那么到这里,本文正式结束。

JS leetcode 存在重复元素 II 题解分析,记一次震惊的负向优化的更多相关文章

  1. 力扣(LeetCode)删除排序链表中的重复元素II 个人题解

    给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字. 思路和上一题类似(参考 力扣(LeetCode)删除排序链表中的重复元素 个人题解)) 只不过这里需要用到一个前 ...

  2. LeetCode 82,考察你的基本功,在有序链表中删除重复元素II

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题的第51篇文章,我们来看LeetCode第82题,删除有序链表中的重复元素II(Remove Duplicates ...

  3. LeetCode 82. 删除排序链表中的重复元素 II(Remove Duplicates from Sorted List II)

    82. 删除排序链表中的重复元素 II 82. Remove Duplicates from Sorted List II 题目描述 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有 ...

  4. Java实现 LeetCode 219 存在重复元素 II(二)

    219. 存在重复元素 II 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k. 示 ...

  5. Java实现 LeetCode 82 删除排序链表中的重复元素 II(二)

    82. 删除排序链表中的重复元素 II 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字. 示例 1: 输入: 1->2->3->3->4- ...

  6. leetcode 83. 删除排序链表中的重复元素 及 82. 删除排序链表中的重复元素 II

    83. 删除排序链表中的重复元素 问题描述 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2 输出: 1->2 示例 2: 输入: ...

  7. 【每日算法】存在重复元素 II

    题目描述 这是 LeetCode 上的 219. 存在重复元素 II, 难度为 [简单] 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nu ...

  8. LeetCode-082-删除排序链表中的重复元素 II

    删除排序链表中的重复元素 II 题目描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字. 返回同样按升序 ...

  9. Leetcode 存在重复元素 (219,220)

    219. 存在重复元素 II 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k. / ...

  10. JS数组去掉重复元素

    JS数组去掉重复元素,这里提供3中写法. var arr =[1,2,3,4,5,6,3,4,7,2,4,1,8]; 输出:[1,2,3,4,5,6,7,8]; 1.使用indexOf() arr.i ...

随机推荐

  1. The requested URL could not be retrieved

    在开发过程中,调用对外接口,返回了一长串的标签提示,如下 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "ht ...

  2. 【TouchGFX 】使用 CubeMX 创建 TouchGFX 工程时 LCD 显示为雪花屏

    经几个晚上折腾,修改大量的LTDC时钟.时序,FMC时序等,结果还是一样,耐心与好使的工程仔细对比,发现是时钟源配置问题,真是冤,聊以此以示纪念 实质上是没有分清有源和无源晶振 无源晶振又被叫做 谐振 ...

  3. 3 分钟了解 NVIDIA 新出的 H200

    英伟达在 2023 年全球超算大会上发布了备受瞩目的新一代 AI 芯片--H200 Tensor Core GPU.相较于上一代产品 H100,H200 在性能上实现了近一倍的提升,内存容量翻倍,带宽 ...

  4. OB指定开源版本MySQL模式单节点安装

    OB指定开源版本MySQL模式单节点安装 yum源处理 yum install -y yum-utils yum-config-manager --add-repo https://mirrors.a ...

  5. [转帖]max_allowed_packet 与 SQL 长度的关系

    https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000210013 适用版本:V2.1.x.V2.2.x.V3.1 ...

  6. [转帖]Elasticsearch 的 30 个调优最佳实践!

    Elasticsearch 的 30 个调优最佳实践! https://zhuanlan.zhihu.com/p/406264041 ES 发布时带有的默认值,可为 es 的开箱即用带来很好的体验.全 ...

  7. 如何查看服务器的Raid缓存等配置的情况

    摘要 最近总遇到同一批机器的IO不一样的情况. 感觉可能跟硬件设备和Raid卡的设置不一样有关系. 所以今天学习研究了下storcli的命令. 希望能够进行一些数据的收集. Storcli简介 sto ...

  8. [转贴]BLOCKED,WAITING,TIMED_WAITING有什么区别?-用生活的例子解释

    BLOCKED,WAITING,TIMED_WAITING有什么区别?-用生活的例子解释 https://www.jianshu.com/p/0976b2f23db1 https://dzone.co ...

  9. Redis-dump Docker搭建的快速指南

    背景 最近学习redis想能够将dump文件进行导入处理. 看到比较好的办法都是使用ruby ,但是公司的网络太感人了. 想着比较简单的办法是通过docker方式来搭建. 这里简单记录一下搭建过程. ...

  10. charles如何抓取https请求

    我们都知道charles下载安装后只能抓取http请求,要想抓取https请求需要下载安装证书 下面介绍pc端和移动端的配置方法 一.pc端(win) 1.打开charles,点击help>SS ...