壹 ❀ 引

整理下今天做的算法题,题目难度不高,但在优化角度也是费了一些功夫。题目来自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. wireshark 显示过滤表达式

    转载请注明出处: 1.根据协议过滤: 在显示过滤表达式的输入框中直接输入对应的协议类型即可:http   tcp  udp 2.根据 IP 过滤: 根据源IP地址过滤:如源地址IP为:127.0.0. ...

  2. 【滤波】Kalman Filter

    from: 卡尔曼滤波教程 (kalmanfilter.net) 总览 关于本教程 关于作者 关于卡尔曼滤波 为什么需要预测算法 卡尔曼滤波简介 必要的背景知识 均值和期望 方差和标准差 正态分布 随 ...

  3. 文心一言 VS 讯飞星火 VS chatgpt (185)-- 算法导论14.1 2题

    二.用go语言,对于图 14-1中的红黑树 T 和关键字 x.key 为35的结点x,说明执行 OS-RANK(T,x) 的过程. 文心一言: 在红黑树中,OS-RANK(T, x) 是一个操作,用于 ...

  4. [javaee] - tomcat 下载和配置环境变量

    以tomcat9为例 第一步 :下载到本地并解压文件 解压后: 第二步:配置环境变量,在系统变量中添加  CATALINE_HOME  ,路径为tomcat的目录 启动tomcat , 启动之后不要关 ...

  5. [转帖]Oracle与防火墙

    https://www.laoxiong.net/oracle_and_firewall.html 老熊 Oracle数据库管理 2009-04-20 最近有两次Oracle数据库故障与防火墙有关.这 ...

  6. [转帖]解决Harbor在服务器重启后无法自启动的问题

    问题 当部署Harbor的服务器在重启之后,可能会出现Harbor无法跟随系统自启动 解决方案 现假设Harbor的安装目录位置为/usr/local/harbor,在Harbor安装完成之后,在此目 ...

  7. [转帖]Tail Latency学习

    https://www.cnblogs.com/Rohn/p/15123758.html Latency,中文译作延迟,Tail Latency即尾延迟. 实际生产中的Latency是一种(概率)分布 ...

  8. [转帖]Nginx动静分离详解以及配置

    https://developer.aliyun.com/article/885602?spm=a2c6h.24874632.expert-profile.314.7c46cfe9h5DxWK 简介: ...

  9. TCP内核参数与Nginx配置的简单测试

    背景 昨天晚上整理了下几个TCP内核的参数. 学习到了一点内核参数的影响. 但是因为时间比较晚了没有继续钻研与nginx的关系 今天想着继续研究一下TCP的部分参数与nginx的关系 每个系统都不一样 ...

  10. React中受控组件与非受控组件的使用

    受控组件 受控组件的步骤: 1.在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源) 2.给表单元素绑定change事件,将表单元素的值设置为state的值(这样就可以控制表 ...