class Solution:
def containsNearbyAlmostDuplicate(self, nums, k, t):
if t < 0 or k <= 0:
return False
bucket = {}
width = t + 1
for i, num in enumerate(nums):
bucket_id = num // width if num >= 0 else ((num + 1) // width) - 1
if bucket_id in bucket:
return True
if bucket_id - 1 in bucket and abs(num - bucket[bucket_id - 1]) <= t:
return True
if bucket_id + 1 in bucket and abs(num - bucket[bucket_id + 1]) <= t:
return True
bucket[bucket_id] = num
if i >= k:
old_bucket_id = nums[i - k] // width if nums[i - k] >= 0 else ((nums[i - k] + 1) // width) - 1
del bucket[old_bucket_id]
return False
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
if (t < 0 || k <= 0) return false;
unordered_map<long, long> bucket;
long width = (long)t + 1;
for (int i = 0; i < nums.size(); ++i) {
long num = (long)nums[i];
long bucket_id = num >= 0 ? num / width : ((num + 1) / width) - 1;
if (bucket.count(bucket_id)) return true;
if (bucket.count(bucket_id - 1) && abs(num - bucket[bucket_id - 1]) <= t) return true;
if (bucket.count(bucket_id + 1) && abs(num - bucket[bucket_id + 1]) <= t) return true;
bucket[bucket_id] = num;
if (i >= k) {
long old_num = (long)nums[i - k];
long old_bucket_id = old_num >= 0 ? old_num / width : ((old_num + 1) / width) - 1;
bucket.erase(old_bucket_id);
}
}
return false;
}
};
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (t < 0 || k <= 0) return false;
Map<Long, Long> bucket = new HashMap<>();
long width = (long)t + 1;
for (int i = 0; i < nums.length; i++) {
long num = (long)nums[i];
long bucketId = num >= 0 ? num / width : ((num + 1) / width) - 1;
if (bucket.containsKey(bucketId)) return true;
if (bucket.containsKey(bucketId - 1) && Math.abs(num - bucket.get(bucketId - 1)) <= t) return true;
if (bucket.containsKey(bucketId + 1) && Math.abs(num - bucket.get(bucketId + 1)) <= t) return true;
bucket.put(bucketId, num);
if (i >= k) {
long oldNum = (long)nums[i - k];
long oldBucketId = oldNum >= 0 ? oldNum / width : ((oldNum + 1) / width) - 1;
bucket.remove(oldBucketId);
}
}
return false;
}
}
var containsNearbyAlmostDuplicate = function(nums, k, t) {
if (t < 0 || k <= 0) return false;
const bucket = {};
const width = t + 1;
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
const bucketId = num >= 0 ? Math.floor(num / width) : Math.floor((num + 1) / width) - 1;
if (bucket.hasOwnProperty(bucketId)) return true;
if (bucket.hasOwnProperty(bucketId - 1) && Math.abs(num - bucket[bucketId - 1]) <= t) return true;
if (bucket.hasOwnProperty(bucketId + 1) && Math.abs(num - bucket[bucketId + 1]) <= t) return true;
bucket[bucketId] = num;
if (i >= k) {
const oldNum = nums[i - k];
const oldBucketId = oldNum >= 0 ? Math.floor(oldNum / width) : Math.floor((oldNum + 1) / width) - 1;
delete bucket[oldBucketId];
}
}
return false;
};
You are given an integer array nums
and two integers k
and t
. Your task is to determine if there exist two distinct indices i
and j
in the array such that:
nums[i]
and nums[j]
is at most t
(|nums[i] - nums[j]| ≤ t
).i
and j
is at most k
(|i - j| ≤ k
).true
if such elements exist, otherwise return false
.
Constraints:
At first glance, the problem asks us to find two numbers within a certain distance in the array (by index) that are also close in value (by at most t
). The brute-force approach would be to check every pair of indices (i, j)
with |i - j| ≤ k
and see if their values are within t
. However, this would be too slow for large arrays.
To optimize, we need a way to quickly check, for each number, if there is a "nearby" number (by index) whose value is also "close" (by at most t
). This is a classic "sliding window" problem, but with the twist that we must also keep track of value proximity, not just index.
The key insight is to maintain a data structure that lets us efficiently:
k
numbers we've seen (the sliding window).t
of it.The most efficient and commonly accepted solution uses bucketization to solve both constraints in O(n) time. Here’s how it works, step by step:
t + 1
. This way, any two numbers that differ by at most t
will either fall into the same bucket or adjacent buckets.
t
difference (since they are in the same bucket).
t
could fall into neighboring buckets, we also check the previous and next buckets for a value within t
.
k
of each other, we remove the element that falls out of the window (i.e., when i ≥ k
, remove nums[i - k]
from its bucket).
true
. If we finish processing the array without finding such a pair, return false
.
We use a hash map to represent buckets for O(1) insert, lookup, and delete operations. The bucket ID is computed carefully to handle negative numbers.
Let's walk through an example:
Input: nums = [1, 5, 9, 1, 5, 9]
, k = 2
, t = 3
At no point do we find two numbers within the same or adjacent buckets that satisfy both constraints. So, the function returns false
.
If, for example, nums = [1, 2, 3, 1]
, k = 3
, t = 0
, then when we reach i = 3
, num = 1, and bucket 1 already contains 1 (from i = 0
), and i - 0 = 3 ≤ k
, so we return true
.
Brute-Force Approach:
i
, check all indices j
such that i < j ≤ i + k
and test if |nums[i] - nums[j]| ≤ t
.n
.k
elements are stored in the bucket map at any time (the window size).
The "Contains Duplicate III" problem combines the sliding window technique with value proximity checks. The elegant solution leverages bucketization to efficiently check, for each number, whether a close-enough value exists nearby in the array. By mapping values to buckets and maintaining a window of size k
, we satisfy both the index and value constraints in linear time. This approach avoids brute-force inefficiency and demonstrates how careful data structure selection can lead to optimal solutions.