力扣220(java)-存在重复元素 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=2147483647
- 0 <= k <= 104
- 0 <= t <= 231 - 1
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/contains-duplicate-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
题解参考@【liweiwei1419】
滑动窗口&红黑树(TreeSet):
题目中的两个要求: 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t,同时又满足abs(i - j) <= k。
1.对于两个位置的下标的范围,可以维护一个长度为 k + 1的滑动窗口(解释:[1,3,5,3,4,6] k =3,滑动窗口的长度可以为4,因为在四个连续的数的下标差一定会小于等于3)。但是滑动窗口中的元素可能为k个,也可能为k+1个,如果先插入新元素则是k+1个,如果是先删除最左端的元素,则是k个。
2.对于两个位置所对应的数字差范围:abs(nums[i] - nums[j]) <= t,可以转换为:-t <= nums[i] - nums[j] <= t,把nums[i]看做新遍历到的元素,nums[j]看做是在滑动窗口里面的元素,将上述不等式可以转换为:
- 条件1:nums[j] >= nums[i] - t;
- 条件2:nums[j] <= nums[i] + t;
条件2的nums[i] + t的值固定,为了让条件2有更多的值成立,条件1找到得到nums[j]需要尽可能的小才行,于是就等价于找大于等于nums[i] - t的最小值,等价于找到nums[i] - t的最小上界。java中的ceiling(key)函数提供了这样的功能:返回大于等于key的最小元素,如果不存在,返回空。
【为什么使用二分搜索树】解释来自【作者:liweiwei1419】
理由 1:由于维护的是固定长度的一系列数,除了最开始的几个数添加进数据结构以外。
当程序看到下标为 k + 1的元素的时候,就需要移除下标为 00 的元素;
当程序看到下标为 k + 2的元素的时候,就需要移除下标为 11 的元素。
频繁的删除和添加元素,符合条件的数据结构是「查找表」,「查找表」的两种实现分别是「哈希表」和「二分搜索树(红黑树)」。
理由 2:根据上面的分析,我们需要找到 nums[i] - t 的最小上界,「哈希表」是不维护元素顺序性的,而「二分搜索树」恰好维护了顺序性,是当前场景下合适的数据结构。且哈希表无法很好的支持范围查询。
注意:
int的溢出问题:例如nums= [-2147483640, -2147483641],k = 1, t = 100,nums[i] - t = -21474836740就会超过int 的最小值范围,故用long 类型来存储。
java代码:
1 class Solution { 2 public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) { 3 TreeSet<Long> set = new TreeSet<>(); 4 for(int i = 0; i < nums.length; i++){ 5 //查找大于等于nums[i]-t的最小值 6 Long ceiling = set.ceiling((long)nums[i] - (long)valueDiff); 7 if(ceiling != null && ceiling <= ((long)nums[i] + (long)valueDiff)){ 8 return true; 9 } 10 set.add((long)nums[i]); 11 //限制滑动窗口的大小 12 if(set.size() == indexDiff + 1){ 13 set.remove((long)nums[i - indexDiff]); 14 } 15 } 16 return false; 17 } 18 }
小知识:
1.java集合中存在以下两种方法:
floor(E e) 方法返回在这个集合中小于或者等于给定元素的最大元素,如果不存在这样的元素,返回null。
ceiling(E e) 方法返回在这个集合中大于或者等于给定元素的最小元素,如果不存在这样的元素,返回null。
2.TreeSet 是一个有序集合,它的作用是提供有序的Set集合。其继承于 AbstractSet 类TreeSet 实现了 NavigableSet 接口,意味着它支持一系列的导航方法。比如查找指定目标最匹配项。
TreeSet 是通过 TreeMap 实现的一个有序的、不可重复的集合,底层维护的是红黑树结构。当TreeSet的泛型对象不是java的基本类型的包装类时,对象需要实现接口Comparable并重写此接口中的compareTo()方法。 TreeSet 实现了 Serializable 接口,所以它支持序列化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)