存在重复元素III

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

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

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

思路:滑动窗口 + 有序集合

对于序列中每一个元素x左侧的至多k个元素,如果这k个元素中存在一个区间[x - t, x + t]中,我们就找到了一对符合条件的元素。注意到对于两个相邻的元素,它们各自的左侧的k个元素中有k - 1个是重合的。于是我们可以使用滑动窗口的思路,维护一个大小为k的滑动窗口,每次遍历到元素x时,滑动窗口中包含元素x前面的最多k个元素,检查窗口中是否存在元素落在区间[x - t, x+ t]。
如果使用队列维护滑动窗口内的元素,由于元素是无序的,我们只能对于每个元素都遍历一次队列来检查是否有元素符合条件。如果数组的长度为n,则使用队列的时间复杂度为O(nk),会超出时间限制。

  • 支持添加和删除指定的操作,如insert和erase操作,否则我们无法维护滑动窗口
  • 内部元素有序,支持二分查找的操作,这样我们可以快速判断滑动窗口中是否存在元素满足条件,具体而言,对于元素x,当我们希望判断滑动窗口中是否存在某个数y落在区间[x - t, x + t]中,只需要判断滑动窗口中所有大于等于x - t的元素中的最小元素是否小于等于x + t即可。

在这里插入图片描述

class Solution {
public: 
	bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t)
	{
		int n = nums.size();
		set<int> rec;
		for (int i = 0; i < n; i++)
		{
		// lower_bound在set中用法:
		// 二分查找一个有序数列,返回第一个大于等于x的数,如果没找到,返回末尾的迭代器位置
		// 比如刚开始rec.lower_bound(nums[i] - t)就是在找滑动窗口rec当中,大于等于nums[i] - t的第一个数,也就是左端点。很明显,一开始rec为空,取lower_bound为0,i = 1,送入一个1之后,rec.lower_bound(2)不存在,返回最后一个数字1,继续插入。由于我们的t=0,每次都是查询跟当前元素相等的元素,作为lower_bound,所以一直都无法得到,直到最后一个数字,1跟最一开始的1,可以查询到,而且还不是最后一个数字。然后进行下一步的判断,1,还小于等于min(1)+0。
			auto iter = rec.lower_bound(max(nums[i], INT_MIN + t) - t);
			if (iter != rec.end() && *iter <= min(nums[i], INT_MAX - t) + t) {
				return true;
			}
			rec.insert(nums[i]);
			if (i >= k){
				rec.erase(nums[i - k]);
			}
		}
		return false;
	}
};

复杂度分析

  • 时间复杂度:O(nlog(min(n, k))),其中
  • 空间复杂度:O(min(n, k)),其中n是给定数组的长度。有序集合中至多包含min(n, k + 1)个元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值