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

壹 ❀ 引
整理下今天做的算法题,题目难度不高,但在优化角度也是费了一些功夫。题目来自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 题解分析,记一次震惊的负向优化的更多相关文章
- 力扣(LeetCode)删除排序链表中的重复元素II 个人题解
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字. 思路和上一题类似(参考 力扣(LeetCode)删除排序链表中的重复元素 个人题解)) 只不过这里需要用到一个前 ...
- LeetCode 82,考察你的基本功,在有序链表中删除重复元素II
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题的第51篇文章,我们来看LeetCode第82题,删除有序链表中的重复元素II(Remove Duplicates ...
- LeetCode 82. 删除排序链表中的重复元素 II(Remove Duplicates from Sorted List II)
82. 删除排序链表中的重复元素 II 82. Remove Duplicates from Sorted List II 题目描述 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有 ...
- Java实现 LeetCode 219 存在重复元素 II(二)
219. 存在重复元素 II 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k. 示 ...
- Java实现 LeetCode 82 删除排序链表中的重复元素 II(二)
82. 删除排序链表中的重复元素 II 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字. 示例 1: 输入: 1->2->3->3->4- ...
- leetcode 83. 删除排序链表中的重复元素 及 82. 删除排序链表中的重复元素 II
83. 删除排序链表中的重复元素 问题描述 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2 输出: 1->2 示例 2: 输入: ...
- 【每日算法】存在重复元素 II
题目描述 这是 LeetCode 上的 219. 存在重复元素 II, 难度为 [简单] 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nu ...
- LeetCode-082-删除排序链表中的重复元素 II
删除排序链表中的重复元素 II 题目描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字. 返回同样按升序 ...
- Leetcode 存在重复元素 (219,220)
219. 存在重复元素 II 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k. / ...
- 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 ...
随机推荐
- 12_前K个高频元素
前K个高频元素 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素.你可以按 任意顺序 返回答案.347.力扣题目链接 示例 1: 输入: nums = [1,1,1, ...
- STM32CubeMX教程23 FSMC - IS62WV51216(SRAM)驱动
1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) 野火DAP仿真器 keil µVision5 IDE(MDK-Arm ...
- [转帖]Oracle 12.2 新特性 | PDB不同字符集变更
https://www.cnblogs.com/cqdba/p/8bef7c432b87807c0680d6791f427b09.html 在oracle12.1版本中,同一CDB中的所有PDB使用的 ...
- [转帖]br 备份时排除某个库
https://tidb.net/book/tidb-monthly/2023/2023-02/usercase/excluded-a-storeroom-during-br-backup 生产环境中 ...
- [转帖][minio]挂载minio到本地
https://www.cnblogs.com/XY-Heruo/p/16489190.html 前言 将minio的bucket挂载到本地文件系统 环境 客户端系统版本:centos 7 MinIO ...
- [转帖]Jmeter性能测试:高并发分布式性能测试
一.为什么要进行分布式性能测试 当进行高并发性能测试的时候,受限于Jmeter工具本身和电脑硬件的原因,无法满足我们对大并发性能测试的要求.基于这种场景下,我们就需要采用分布式的方式来实现我们高并发的 ...
- [转帖]Postmark - 存储性能测试工具
1. 引言 Postmark是由著名的NAS提供商NetApp开发,用来测试其产品的后端存储性能. Postmark主要用于测试文件系统在邮件系统或电子商务系统中性能,这类应用的特点是:需要频繁.大量 ...
- [转帖]Java 类加载器
类的生命周期和加载过程 在Java中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载.引用类型,Java 将其细分为四种:类.接口.数组类和泛型 ...
- 解决刷新SwaggerUi控制台报错
一.问题描述 在浏览器刷新SwaggerUI的页面,控制台就报错: java.lang.NumberFormatException: For input string: "" at ...
- vue在render函数中如何实现v-model和事件绑定(4)
1.h函数的三个参数 第一个参数是必须的. 类型:{String | Object | Function} 一个 HTML 标签名.一个组件.一个异步组件.或一个函数式组件. 是要渲染的html标签. ...