壹 ❀ 引

今天的题目来自LeetCode 220. 存在重复元素 III,难度中等,题目描述如下:

给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。

如果存在则返回 true,不存在返回 false。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 0 <= nums.length <= 2 * 104
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 104
  • 0 <= t <= 231 - 1

贰 ❀ 暴力解法

我们简单分析题意,给定一个数组以及两个整数k与t,判断是否存在两个不同的下标i,j,满足abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k,存在返回true,反之返回false。

那么根据提议,我们完全可以根据题目要求的条件直接暴力解决:

/**
* @param {number[]} nums
* @param {number} k
* @param {number} t
* @return {boolean}
*/
var containsNearbyAlmostDuplicate = function (nums, k, t) {
// i从0开始
for (let i = 0; i < nums.length; i++) {
// j永远从i后一位开始
for (let j = i + 1; j < nums.length; j++) {
// 直接抽取题目要求的条件
if (Math.abs(nums[i] - nums[j]) <= t && Math.abs(i - j) <= k) {
return true;
};
};
};
return false;
};

代码就不解释了,暴力解法非常简单。

贰 ❀ 桶排序

老实说,桶排序的做法比较难理解(当然也许是因为我太菜了),我在题解区逛了一圈,发现大部分题解说的基本都半知半解,看的我是非常难受和....也不知道是不是语言差异,部分思路在JS中也行不通,这里我也是读了好几篇题解,综合了一下他们的思想,整理下我最终的理解,其中思路来源主要来自C++ 利用桶分组, 详细解释

在我贴的参考思路题解中,举了一个我觉得十分恰当的例子,这里我简单重复下,假设有一批学生都是同一年不同月出生,老师要找出出生期相差在30天以内的学生,这里的30天可以理解为题目中的参数t,即两个学生出生日期的差小于等于30。那么我们大致能掌握这样一个规律:

  • 同一个月出生的学生一定满足条件,比如3月5号,3月15,都在一个月内总不能超出一个月的时间差吧。
  • 某个月相邻的前后两个月的学生可能满足条件,比如3月5号,2月15号就满足一个月内,但2月1号就不行了,超出了一个月,所以是可能满足。
  • 间隔超过2个月的学生绝对不可能满足条件,比如3月出生的学生和1月或者5月的学生都不可能满足条件。

那么知道了这个规则,我们要做的就是将这些学生按月份进行分配,3月出生的学生都在一起,4月的也都在一起,这一个个月份就像一个桶,我们将学生装进了桶里。相同桶里学生的日期差一定小于30(t),我们可以总是至多维护3个桶,这个3可以理解为题意中的参数k,因为规则2也说了,维护四个桶没意义,要找有没有符合条件的还是得从自己,活着相邻的桶里去看有没有符合规则的。

OK,上面的例子整体会比较抽象,但大致阐述了这么一个想法,你能大概明白是什么意思就足够了,那么我们现在要思考一个问题,我们怎么知道哪些学生应该放在某个桶呢?

这里我先给出一个公式,再论证它:

// x可以理解成学生的出生日期,t可以理解成我们定下的规则,也就是小于等于30天
let bucketNum = Math.floor(x / (t + 1))

现在开始论证,我们假设学生的年龄为[0,1,2,3,4,5,6],t是3,即数字之间相差小于等于3的应该放在一起。

Math.floor(0 / (3 + 1))//0
Math.floor(1 / (3 + 1))//0
Math.floor(2 / (3 + 1))//0
Math.floor(3 / (3 + 1))//0 Math.floor(4 / (3 + 1))//1
Math.floor(5 / (3 + 1))//1
Math.floor(6 / (3 + 1))//1

你会发现[0,1,2,3]四个数在这个公式中都等于0,说明它们四个应该放在一起,而且仔细推敲,这四个数的差的绝对值还真是小于等于3。但是4就不能加进去了,因为4-0>3[4,5,6]同理又被分配在了一个桶里。

我看一些题解给的公式是x / t然后又说要加1,但没具体说为什么要加个1,其实根据题意中0 <= t <= 231 - 1,假设t=0,万物除以0都是无限大,就无法区分了,而且站在JS的角度,如果我们不加1,你会发现[0,1,2]会被丢在一个桶,因为:

Math.floor(0 / 3)//0
Math.floor(1 / 3)//0
Math.floor(2 / 3)//0
Math.floor(3 / 3)//1

3根本就不会放进0号桶,这根本就满足不了条件了,所以公式是一定得加个1(这也是为什么我看一些人题解看的特别恼火的原因,写的不明不白,看的很无语)。

那么我们假设有数组[1,2,3,1]k=3t=0,即在这个数组中,是否存在两个数的下标差绝对值小于等于3,且两个数的差的绝对值小于等于0。我们尝试推导这个过程:

i=0时,取到数字1,由于Math.floor(1 / (0 + 1))为1,我们需要创建1号桶,并把0这个元素作为value存起来。

i=1时,取到数字2,由于Math.floor(2 / (0 + 1))为2,桶不同说明数字相差绝对超过2了,如果满足0,绝对在同一个桶,一样创建2号桶,把2存起来

继续,当i=2时,取到了数字3,由于Math.floor(3 / (0 + 1))为3,说明相差又超过0了,继续上面的操作。

i=3时,取到了数字1,由于Math.floor(3 / (0 + 1))为0,我们发现0号桶已经存在了,说明0号桶和当前的数字相差一定小于等于0,而且i此时为3,3并不比k大,说明下标没超过条件,满足最终返回true。

等等,这个例子好像过于理想,假设我们再多一点呢?比如[1,2,3,4,1],k和t不变:

前三步还是一样,创建了1,2,3,一共三个桶。

i=3时,由于Math.floor(4 / (0 + 1))为4,所以创建了4号桶,我们发现此时仍然没找到符合条件的数字,注意由于此时i>=k已经是下标差的最大极限了,在最大极限情况下还没找到满足条件,我们得删除掉一个桶。为什么?你想想,假设我们不删除,走到i=4时,由于Math.floor(1 / (0 + 1))为1,我们发现它和1=0时得到的是相同的桶,那么说明两个元素差的绝对值一定<=t,但是很遗憾i>k了,此时的i是4,所以就算桶相同,你的下标已经不符合了啊,那这个桶的比较又有什么意义呢?所以在i>=k的时候,此时你的桶还有参与比较的资格,但如果还不满足,那就得删除掉最早创建的桶,为下次比较做准备。

有同学就有疑问了,删掉了不会对后续的比较产生影响吗?我们假设[1,2,3,4,1,1],kt不变,前面几步还是相同,当i=3时不满足,比较完成我们把i=0创建的1号桶给删除了。当i=4其实又创建了1号桶,而当i=5我们还是找到了符合规则的数字,最终返回了true。

以上长篇大论,其实我们还只是推导了学生出生日期的规则1,即如果一个桶被重复创建两次(两个数字在一个桶和一个桶被创建两次是同一个意思),然后下标差还小于等于k,那么这两个数一定符合条件。

别忘了,我们还有规则2,也就是去相邻的桶里找。比如[3][4,5,6]k=3t=1,3和4虽然在不同的桶,但是它们缺满足下标差以及数字差的条件限制。

总结一下:

当创建一个桶,如果桶已存在(说明数字差小于等于t),且下标没超出k,那就返回true。

如果创建一个桶,桶不存在,那就创建好这个桶,记录好数字,同时看看左右相邻的桶里的数求差,看看能不能满足条件。注意,左右相邻的桶一定也只会有一个数字,为啥呢?因为如果左右相邻的桶存在2个数字,那就满足了上一条规则,已经返回true了...

如果以上都比较完了,这时候看看i跟k的关系,如果满足了i>=k,那你得删除最早创建的桶了。

那么,贴上代码:

/**
* @param {number[]} nums
* @param {number} k
* @param {number} t
* @return {boolean}
*/
var containsNearbyAlmostDuplicate = function (nums, k, t) {
// 计算桶编号
function getBucketNum(x) {
return Math.floor(x / (t + 1));
};
// 创建一个大桶,里面用于存放一些小桶
let buckets = new Map();
for (let i = 0; i < nums.length; i++) {
// m是当前遍历元素将要在的桶
const bucket = getBucketNum(nums[i]);
// 此时桶的数量一定是被维护好的,如果一个桶已经存在,说明一定满足条件。
if (buckets.has(bucket)) {
return true;
// 比较右边的桶,看看差是否满足条件
} else if (buckets.has(bucket + 1) && Math.abs(buckets.get(bucket + 1) - nums[i]) <= t) {
return true;
// 同理比较左边
} else if (buckets.has(bucket - 1) && Math.abs(buckets.get(bucket - 1) - nums[i]) <= t) {
return true;
}
// 保存这个桶,以及桶的数字
buckets.set(bucket, nums[i]);
// 如果i>=k,能走到这一步,满足条件的最后一个桶都比较完了,还不符合,那就得删除最早的桶,为下次比较做准备
if (i >= k) {
buckets.delete(getBucketNum(nums[i - k]));
}
}
return false;
};

这道题桶排序的题解,我想想,前前后后大概整理加思考了差不多花了4个小时,确实很绕...如果有幸看到这篇题解,还是静下心来理一理,那么本文结束。

JS Leetcode 220. 存在重复元素 III 题解分析,暴力解法与桶排序的更多相关文章

  1. Java实现 LeetCode 220 存在重复元素 III(三)

    220. 存在重复元素 III 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最 ...

  2. Leetcode 220.存在重复元素III

    存在重复元素III 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ. ...

  3. [LeetCode]220. 存在重复元素 III

    题目链接:https://leetcode-cn.com/problems/contains-duplicate-iii/ 题目描述: 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使 ...

  4. 220. 存在重复元素 III

    题目: 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ. 示例 1: ...

  5. 【每日算法】存在重复元素 III

    题目描述 这是 LeetCode 上的 220. 存在重复元素 III, 难度为 [中等] 给你一个整数数组 nums 和两个整数 k 和 t .请你判断是否存在 两个不同下标 i 和 j,使得 ab ...

  6. LeetCode:存在重复元素【217】

    LeetCode:存在重复元素[217] 题目描述 给定一个整数数组,判断是否存在重复元素. 如果任何值在数组中出现至少两次,函数返回 true.如果数组中每个元素都不相同,则返回 false. 示例 ...

  7. leetcode——217. 存在重复元素

    leetcode--217. 存在重复元素 题目描述:给定一个整数数组,判断是否存在重复元素. 如果存在一值在数组中出现至少两次,函数返回 true .如果数组中每个元素都不相同,则返回 false ...

  8. [LeetCode] 220. Contains Duplicate III 包含重复元素 III

    Given an array of integers, find out whether there are two distinct indices i and j in the array suc ...

  9. [LeetCode]-217.存在重复元素-简单

    217. 存在重复元素 给定一个整数数组,判断是否存在重复元素. 如果存在一值在数组中出现至少两次,函数返回 true .如果数组中每个元素都不相同,则返回 false . 示例 1: 输入: [1, ...

  10. JS Jquery去除数组重复元素

    js jquery去除数组中的重复元素 第一种:$.unique() 第二种: for(var i = 0,len = totalArray_line.length;i < len;i++) { ...

随机推荐

  1. WPF|分享一个登录界面设计

    分享一个登录界面,先看效果图: 准备 文中使用到了一些图标: 我们可以从 iconfont免费下载: 代码简单说明 请随手创建一个WPF项目(.NET Framework..NET 5\6\7皆可), ...

  2. JMS微服务开发示例(六)安全退出进程

    默认情况下,如果在linux,需要关闭微服务进程,请务必使用 kill -15 进程id 命令,其他命令可能会直接关闭进程,造成数据丢失. 例如,有个后台任务,执行了一半,这时候进程突然关闭了,会形成 ...

  3. [转帖]Linux中的lstopo命令(详细指南)

    https://juejin.cn/post/7117544110856077343 目录: 简介 语法 命令 总结 参考文献 介绍 lstopo命令是用来显示系统的拓扑结构的.它提供了关于NUMA内 ...

  4. [转帖]Jmeter学习笔记(九)——响应断言

    Jmeter学习笔记(九)--响应断言 https://www.cnblogs.com/pachongshangdexuebi/p/11571348.html Jmeter中又一个元件叫断言,用于检查 ...

  5. 【转帖】Linux中如何取消ln链接?(linuxln取消)

    https://www.dbs724.com/163754.html Linux系统使用ln命令可以快速创建链接,ln链接是指把文件和目录链接起来,当改变源时可以快速地改变整个目录下的文件和目录.有时 ...

  6. [转帖]Web性能优化工具WebPageTest(一)——总览与配置

    https://www.cnblogs.com/strick/p/6677836.html 网站性能优化工具大致分为两类:综合类和RUM类(实时监控用户类),WebPageTest属于综合类. Web ...

  7. [转帖]Linux:页表中PGD、PUD、PMD、TLB等概念介绍

    1.PGD: Page Global Directory        Linux系统中每个进程对应用户空间的pgd是不一样的,但是linux内核 的pgd是一样的.当创建一个新的进程时,都要为新进程 ...

  8. Linux用户以及ssh安全相关设置

    Linux用户相关操作 摘要 最近重保, 需要进行网络安全防护. 部分同事处理过程总是顺序有一些不太对的情况. 同时发现自对Linux用户设置也存在很多不清不楚的地方 所以趁着周末学习和总结一下. 用 ...

  9. [转帖]深入内存/主存:解剖DRAM存储器

    https://zhuanlan.zhihu.com/p/561501585 2022/9/9更新:经过和评论区大佬的交流,准备研读一下JEDEC标准,主要是加深自己对banking和访存加速的理解( ...

  10. [译]深入了解现代web浏览器(一)

    本文是根据Mariko Kosaka在谷歌开发者网站上的系列文章https://developer.chrome.com/blog/inside-browser-part1/ 翻译而来,共有四篇,该篇 ...