Given an array of integers, find out whether there are two distinct indices i and j in the array such that the difference between nums[i] and nums[j] is at most t and the difference between i and j is at most k.
这道题如果用双重for循环提交时会出现time exceed,因为时间复杂度是O(nk),如果k > n,就会变成O(N2),所以需要减少时间复杂度。这里用TreeSet类可以做到O(nlogk)。
public class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (nums.length < 2 && k <= 0 && t < 0) {
return false;
}
TreeSet<Integer> values = new TreeSet<Integer>();
for (int i = 0; i < nums.length; i ++) {
Integer floor = values.floor(nums[i] + t);
Integer ceil = values.ceiling(nums[i] - t);
if ((floor != null && floor >= nums[i]) || (ceil != null && ceil <= nums[i])) {
return true;
}
values.add(nums[i]);
if (i >= k) {
values.remove(nums[i - k]);
}
}
return false;
}
}
Top1用的HashMap,做到了O(n)复杂度,实在厉害,核心思想是把这些数字放到筐里,每个筐大小是 t 或者 t+1,映射时最好选择选择 num/t+1 (t 可能为0),这样同框的数肯定相距最多是 t,紧接着判断左右临筐的数字,是不是符合要求相距小于 t。如果所有数单纯的
num/t+1,这样是趋近于零,考虑负数的话,零的筐装的会是 2t 个元素,所以可以让所有的数从 Integer.MIN_VALUE 开始,即 num - Integer.MIN_VALUE。附上原哥们儿的解释和代码。
As a followup question, it naturally also requires maintaining a window of size k. When t == 0, it reduces to the previous question so we just reuse the solution.
Since there is now a constraint on the range of the values of the elements to be considered duplicates, it reminds us of doing a range check which is implemented in tree data structure and would take O(LogN) if a balanced tree structure is used, or doing a bucket check which is constant time. We shall just discuss the idea using bucket here.
Bucketing means we map a range of values to the a bucket. For example, if the bucket size is 3, we consider 0, 1, 2 all map to the same bucket. However, if t == 3, (0, 3) is a considered duplicates but does not map to the same bucket. This is fine since we are checking the buckets immediately before and after as well. So, as a rule of thumb, just make sure the size of the bucket is reasonable such that elements having the same bucket is immediately considered duplicates or duplicates must lie within adjacent buckets. So this actually gives us a range of possible bucket size, i.e. t and t + 1. We just choose it to be t and a bucket mapping to be num / t.
Another complication is that negative ints are allowed. A simple num / t just shrinks everything towards 0. Therefore, we can just reposition every element to start from Integer.MIN_VALUE.
public class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (k < 1 || t < 0) return false;
Map<Long, Long> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
long remappedNum = (long) nums[i] - Integer.MIN_VALUE;
long bucket = remappedNum / ((long) t + 1);
if (map.containsKey(bucket)
|| (map.containsKey(bucket - 1) && remappedNum - map.get(bucket - 1) <= t)
|| (map.containsKey(bucket + 1) && map.get(bucket + 1) - remappedNum <= t))
return true;
if (map.entrySet().size() >= k) {
long lastBucket = ((long) nums[i - k] - Integer.MIN_VALUE) / ((long) t + 1);
map.remove(lastBucket);
}
map.put(bucket, remappedNum);
}
return false;
}
}
Edits:
Actually, we can use t + 1 as the bucket size to get rid of the case when t == 0. It simplifies the code. The above code is therefore the updated version.