壹 ❀ 引

今天的题目来自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. SV 数据类型-3

    联合数组 在内存中分配的空间可以是不连续的 联合数组方法 数组的方法 数组使用推荐 结构体 枚举类型 字符串变量类型String 操作符

  2. 【一文秒懂】Ftrace系统调试工具使用终极指南

    [一文秒懂]Ftrace系统调试工具使用终极指南 1.Ftrace是什么 Ftrace是Function Trace的简写,由 Steven Rostedt 开发的,从 2008 年发布的内核 2.6 ...

  3. 远程复制文件-scp

  4. [转帖]TiDB损坏多副本之有损恢复处理方法

    https://tidb.net/blog/b1ae4ee7   TiDB分布式数据库采用多副本机制,数据副本通过 Multi-Raft 协议同步事务日志,确保数据强一致性且少数副本发生故障时不影响数 ...

  5. [转帖]INSERT IGNORE INTO 与 INSERT INTO

    INSERT IGNORE INTO 会忽略数据库中已经存在 的数据,如果数据库没有数据,就插入新的数据,如果有数据的话就跳过当前插入的这条数据.这样就可以保留数据库中已经存在数据,达到在间隙中插入数 ...

  6. [转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

    使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术.动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就 ...

  7. Mysql8.0.32 union all创建视图无法使用中文模糊查询的坑

    Mysql8.0.32 union all创建视图无法使用中文模糊查询的坑 摘要 本周研发同事反馈现场有一个问题. 客户使用mysql的数据库(Windows平台) 然后在多表union all 创建 ...

  8. 【转帖】3.JVM内存结构概述

    目录 1.JVM内存结构 1.JVM内存结构 在JVM系列的第一篇文章中已经给出了JVM内存结构的简图,下面是JVM内存结构更加详细的图. 同样,JVM的内存结构可以分为上中下3层. 上层主要是类加载 ...

  9. Python处理Oracle数据库的学习过程

    Python处理Oracle数据库的学习过程 背景 产品数据存在一些大小写敏感的数据迁移到不敏感的数据库时出现报错的情况. 基于此, 我这边跟帅男同学学习了下Python的使用. 因为这一块一直比较菜 ...

  10. 真·Android Zxing 扫码中文乱码解决

    Zxing3.2.1 之前遇到过Android Zxing扫码乱码的问题,网上搜了下解决了下. 今天又遇到了问题. 依然会乱码. 研究总结如下: Zxing 可以在Hints中添加默认编码格式.这个编 ...