壹 ❀ 引

今天的题目来自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. java进阶(13)--int、String、Integer互相转换

    一.转换流程图  

  2. Postman 接口测试配置 Pre-request Script

    本文为博主原创,转载请注明出处:  Pre-request Script 为Postman预置脚本,用于在postman 发送请求之前执行,封装计算或获取某些请求参数. 1. postman 脚本提供 ...

  3. spring启动流程 (3) BeanDefinition详解

    BeanDefinition在Spring初始化阶段保存Bean的元数据信息,包括Class名称.Scope.构造方法参数.属性值等信息,本文将介绍一下BeanDefinition接口.重要的实现类, ...

  4. [转帖]wmic命令介绍

    https://www.jianshu.com/p/3e1a5a8fa23b How to Get Your System Serial Number PS C:\windows\system32&g ...

  5. [转帖]010 Linux 文本统计与去重 (wc 和 uniq)

    https://my.oschina.net/u/3113381/blog/5427461 wc 命令一般是作为组合命令的一员与其他命令一同起到统计的作用.而一般情况下使用 wc -l 命令较多. u ...

  6. [转帖]CKA 真题

    https://segmentfault.com/a/1190000021380185   1.列出pod并排序 kubectl get pod --sort-by .metadata.name 题目 ...

  7. [转帖]讨论在 Linux Control Groups 中运行 Java 应用程序的暂停问题原创

    https://heapdump.cn/article/1930426 说明 本篇原文来自 LinkedIn 的 Zhenyun Zhuang,原文:Application Pauses When R ...

  8. Unity下调试ToLua(基于IDEA和VSCode)

    公司移动端项目是基于Unity的,底层支持由C#提供,上层Lua调用C#中注册的函数支持来做业务逻辑,框架用的是ToLua.开始做移动端有一段时间了,一直都觉得调试代码是个很蛋疼的体验:几乎都是靠肉眼 ...

  9. 高性能MySQL实战(二):索引 | 京东物流技术团队

    我们在上篇 高性能MySQL实战(一):表结构 中已经建立好了表结构,这篇我们则是针对已有的表结构和搜索条件为表创建索引. 1. 根据搜索条件创建索引 我们还是先将表结构的初始化 SQL 拿过来: C ...

  10. 感受 Vue3 的魔法力量

    ​ 作者:京东科技 牛至伟 近半年有幸参与了一个创新项目,由于没有任何历史包袱,所以选择了Vue3技术栈,总体来说感受如下: • setup语法糖<script setup lang=" ...