问题链接:点击打开链接
Given an array of integers, find out whether there are two distinct indices i and j in the array such that the absolute difference betweennums[i] and nums[j] is at most t and the absolute difference between i and j is at most k.
Subscribe to see which companies asked this question.
最直接的方式是对数组前nums.length-k个元素进行与其前面t个元素值进行比较的方式进行计算,如果找到则直接返回true结束运算,否则,计算结束后返回false表示未找到。
public class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if(nums==null || nums.length==1)
return false;
if(k > nums.length) k=nums.length;
for(int i=0;i<nums.length-k;i++) {
for(int j=1;(j<=k) && (i+j < nums.length);j++) {
if(Math.abs(nums[i]-nums[i+j])<=t)
return true;
}
}
return false;
}
}
但是看到解答里的采用TreeSet的方式觉得比较巧妙,用到了TreeSet的floor()、ceil()两个方法,分别含义是:
floor(value)表示在这个TreeSet里小于等于value的最大值,
ceil(value)表示大于等于value的最小值。若存在则返回值,否则返回null
下面的例子展示java.util.TreeSet.floor()方法的使用。
TreeSet <Integer>treeadd = new TreeSet<Integer>();
// adding in the tree set
treeadd.add(12);
treeadd.add(11);
treeadd.add(16);
treeadd.add(15);
// getting floor and ceiling value for 12
System.out.println("\nfloor value for 12: "+treeadd.floor(12));
System.out.println("Ceiling value for 12: "+treeadd.ceiling(12));
// getting floor and ceiling value for 13
System.out.println("\nfloor value for 13: "+treeadd.floor(13));
System.out.println("Ceiling value for 13: "+treeadd.ceiling(13));
System.out.println("Ceiling value for 20: "+treeadd.ceiling(20));
现在编译和运行上面的代码示例,将产生以下结果。
floor value for 12: 12 Ceiling value for 12: 12 floor value for 13: 12 Ceiling value for 13: 15
Ceiling value for 20: null参照答案编写的程序如下:
This problem requires to maintain a window of size k of the previous values that can be queried for value ranges. The best data structure to do that is Binary Search Tree. As a result maintaining the tree of size k will result in time complexity O(N lg K). In order to check if there exists any value of range abs(nums[i] - nums[j]) to simple queries can be executed both of time complexity O(lg K)
Here is the whole solution using TreeMap.
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if(nums==null || nums.length==1 || k<=0)
return false;
TreeSet<Integer> treeSet = new TreeSet<>();
for(int i=0;i<nums.length;i++) {
Integer floor = treeSet.floor(nums[i]+t);
Integer ceil = treeSet.ceiling(nums[i]-t);
if((floor!=null && floor>=nums[i]) || (ceil!=null && ceil<=nums[i]))
return true;
treeSet.add(nums[i]);
if(i>=k) {
treeSet.remove(nums[i-k]);
}
}
return false;
}
但是运行时,提示
Submission Result: Wrong Answer More Details
但是该种解决问题的思路仍然值得学习和掌握。
还有一种解决思路:
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.