前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Leetcode No.220 存在重复元素 III(桶排序)

Leetcode No.220 存在重复元素 III(桶排序)

作者头像
week
发布2022-01-06 10:29:01
2900
发布2022-01-06 10:29:01
举报
文章被收录于专栏:用户画像

一、题目描述

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

代码语言:javascript
复制
示例 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

二、解题思路

我们也可以使用利用桶排序的思想解决本题。我们按照元素的大小进行分桶,维护一个滑动窗口内的元素对应的元素。

对于元素 x,其影响的区间为 [x−t,x+t]。于是我们可以设定桶的大小为 t+1。如果两个元素同属一个桶,那么这两个元素必然符合条件。如果两个元素属于相邻桶,那么我们需要校验这两个元素是否差值不超过 t。如果两个元素既不属于同一个桶,也不属于相邻桶,那么这两个元素必然不符合条件。

具体地,我们遍历该序列,假设当前遍历到元素 x,那么我们首先检查 x 所属于的桶是否已经存在元素,如果存在,那么我们就找到了一对符合条件的元素,否则我们继续检查两个相邻的桶内是否存在符合条件的元素。

实现方面,我们将 int 范围内的每一个整数 x 表示为 x=(t+1)×a+b(0≤b≤t) 的形式,这样 x 即归属于编号为 a 的桶。因为一个桶内至多只会有一个元素,所以我们使用哈希表实现即可。

如何理解 getID() 的逻辑

为什么 size 需要对 t 进行 +1 操作? 目的是为了确保差值小于等于 t 的数能够落到一个桶中。

举个 🌰,假设 [0,1,2,3],t = 3,显然四个数都应该落在同一个桶。

如果不对 t 进行 +1 操作的话,那么 [0,1,2] 和 [3] 会被落到不同的桶中,那么为了解决这种错误,我们需要对 t 进行 +1 作为 size 。

这样我们的数轴就能被分割成:

0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 | …

总结一下,令 size = t + 1 的本质是因为差值为 t 两个数在数轴上相隔距离为 t + 1,它们需要被落到同一个桶中。

当明确了 size 的大小之后,对于正数部分我们则有 idx = nums[i] / size。

如何理解负数部分的逻辑?

由于我们处理正数的时候,处理了数值 0,因此我们负数部分是从 -1 开始的。

还是我们上述 🌰,此时我们有 t = 3 和 size = t + 1 = 4。

考虑 [-4,-3,-2,-1] 的情况,它们应该落在一个桶中。

如果直接复用 idx = nums[i] / size 的话,[-4] 和 [-3,-2,-1] 会被分到不同的桶中。

根本原因是我们处理整数的时候,已经分掉了数值 0。

这时候我们需要先对 nums[i] 进行 +1 操作(即将负数部分在数轴上进行整体右移),即得到 (nums[i] + 1) / size。

这样一来负数部分与正数部分一样,可以被正常分割了。

但由于 0 号桶已经被使用了,我们还需要在此基础上进行 -1,相当于将负数部分的桶下标(idx)往左移,即得到 ((nums[i] + 1) / size) - 1。

三、代码

代码语言:javascript
复制
public class Solution2 {
    //k:下标差值
    //t:val差值
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int n = nums.length;
        Map<Long, Long> map = new HashMap<Long, Long>();
        //桶的大小为t+1
        long w = (long) t + 1;
        for (int i = 0; i < n; i++) {
            long id = getID(nums[i], w);
            //我们首先检查 x 所属于的桶是否已经存在元素,如果存在,那么我们就找到了一对符合条件的元素
            if (map.containsKey(id)) {
                return true;
            }
            //检查左侧相邻的桶内是否存在符合条件的元素
            if (map.containsKey(id - 1) && Math.abs(nums[i] - map.get(id - 1)) < w) {
                return true;
            }
            //检查右侧相邻的桶内是否存在符合条件的元素
            if (map.containsKey(id + 1) && Math.abs(nums[i] - map.get(id + 1)) < w) {
                return true;
            }
            //建立目标桶
            map.put(id, (long) nums[i]);
            //删除下标范围不在 [max(0,i−k),i) 内的桶
            if (i >= k) {
                map.remove(getID(nums[i - k], w));
            }
        }
        return false;
    }
    //我们将int范围内的每一个整数x表示为x=(t+1)×a+b(0≤b≤t) 的形式,这样x即归属于编号为a的桶。a=(x-b)/(t+1)
    //因为一个桶内至多只会有一个元素,所以我们使用哈希表实现即可。
    public long getID(long x, long w) {
        if (x >= 0) {
            return x / w;
        }
        return (x + 1) / w - 1;
    }

    public static void main(String[] args) {
        //int[] nums={1,5,9,1,5,9};
        int[] nums={-2147483648,2147483647};
        Solution2 solution=new Solution2();
        System.out.println(solution.containsNearbyAlmostDuplicate(nums,1,1));
    }
}

四、复杂度分析

时间复杂度:O(n),其中 n 是给定数组的长度。每个元素至多被插入哈希表和从哈希表中删除一次,每次操作的时间复杂度均为O(1)。

空间复杂度:O(min(n,k)),其中 n 是给定数组的长度。哈希表中至多包含min(n,k+1) 个元素。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/10/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、题目描述
  • 二、解题思路
    • 如何理解 getID() 的逻辑
      • 如何理解负数部分的逻辑?
      • 三、代码
      • 四、复杂度分析
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档