壹 ❀ 引

整理下今天做的算法题,题目难度不高,但在优化角度也是费了一些功夫。题目来自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. go Print 和 反射

    0. 前言 在 小白学标准库之反射 reflect 篇中介绍了反射的三大法则.但并未给出具体示例介绍反射,感觉还是少了点什么.这里进一步通过fmt.Println 源码,查看反射如何使用的,算是对前文 ...

  2. KVM 核心功能:CPU 虚拟化

    1 vCPU 简介 CPU 负责计算机程序指令的执行.QEMU-KVM 提供对虚拟机 CPU 的模拟,对于虚拟机来说,其拥有的 CPU 是真实的, 和物理 CPU 没有区别. 实际上,虚拟机在 hos ...

  3. spring注入的几种方式

    本文为博主原创,未经允许不得转载: Spring注入有以下几种方式: 构造方法注入:通过构造方法实现依赖注入.在类的构造方法中使用@Autowired注解注入需要的依赖类. Setter方法注入:通过 ...

  4. jvisualm 结合 visualGC 进行jvm监控,并分析垃圾回收

    本文为博主原创,未经允许不得转载 1.jvisualm 的使用 打开 jdk 安装目录bin目录下的 jvisualvm.exe 工具 2. visual GC插件的安装及监控分析 Visual GC ...

  5. 基于python+django的外卖点餐网站-外卖点餐系统

    该系统是基于python+django开发的外卖点餐系统.适用场景:大学生.课程作业.毕业设计.学习过程中,如遇问题可以在github给作者留言. 演示地址 前台地址: http://food.git ...

  6. Redis服务端辅助的客户端缓存机制

    一.背景和问题 二.Redis6 的解决方案及原理 2.1 服务端支持客户端缓存的两种模式 1. 默认模式 2. 广播模式 2.2 客户端实现的两种连接模式 1. 使用同一连接 2. 使用不同连接 3 ...

  7. [转帖][大数据]ETL之增量数据抽取(CDC)

    https://www.cnblogs.com/johnnyzen/p/12781942.html 目录 1 CDC 概念 1.1 定义 1.2 需求背景 1.3 考察指标 2 CDC 常见解决方案 ...

  8. [转帖]s3fs

    https://github.com/s3fs-fuse/s3fs-fuse s3fs allows Linux, macOS, and FreeBSD to mount an S3 bucket v ...

  9. [转帖]《Linux性能优化实战》笔记(20)—— 使用 tcpdump 和 Wireshark 分析网络流量

    tcpdump 和 Wireshark 是最常用的网络抓包和分析工具,更是分析网络性能必不可少的利器. tcpdump 仅支持命令行格式使用,常用在服务器中抓取和分析网络包.Wireshark 除了可 ...

  10. [转帖]Redis中的Lua脚本

    最近琢磨分布式锁时接触到的知识点,简单记一下. 文章目录 1. Redis中的Lua 2. 利用Lua操作Redis 3. Lua脚本的原子性 4. 关于 EVALSHA 5. 常用`SCRIPT` ...