给定一个整数数组,找出是否有两个不同的 数组中的索引i和j使得nums [i]之间的差异 和n [j]最多为t,i和j之间的差异最大 ķ。
您好!
但是老实说,我对这个问题感到很难过。在这个问题的(LeetCode)讨论论坛中提供的解决方案没有提供太多关于如何解决它的解释/思考过程。我完全理解解决问题的技术,而不是给我完整的实现代码。我觉得这是最好的学习方式。因此,这里的线索是使用(Java)TreeSet来解决这个问题。我假设地板和天花板方法在这里很有用。
如果有人在那里可以给我一点线索/暗示来解决这个问题,我会很感激。伪代码也很受欢迎!我之前没有说过,我不需要完整的实施代码。只是一个起点很棒! :)
谢谢!
编辑:我同时也在努力解决这个问题!所以,如果我最终得到答案,我会在这里发布以供将来参考。 :)答案 0 :(得分:3)
这个问题已经很老了,但是OP还没有公布他/她的回答...... 我会尝试向那些偶然发现这个问题的人解释。
以下答案基于存储桶,时间复杂度为 O(n)。
基本思想是使用宽度为k的滑动窗口。我们的注意力将在此窗口中受到约束,因此此窗口中i和j(索引)之间的差异不能大于k。 我们检查这个窗口中是否有两个数字,最多不同于t。如果有这样的数字,那么我们就完成了。否则,我们的窗口将向前移动一步,我们将再次检查。
现在真正的问题是我们如何检查窗口中是否有两个这样的数字 。当然我们可以使用残余力,它将是O(K ^ 2),那么整个算法将是O(n * K ^ 2)。如果K不大,我们可以接受。
然而,通过使用桶,我们可以做得更好!
每当我们在窗口中遇到一个数字时,我们将它除以(t + 1)。假设结果是B,我们然后将数字放入桶[B]。
如果t = 3,那么数字0,1,2,3将全部放入桶[0],数字4,5,6,7将被放入桶[1]和8,9,10, 11将被放入桶[2]等。我们保证一个桶中的所有数字都会有不大于t的差异。还有一件事:4 - 2 = 2< 3他们在不同的桶里。是的,一些差异小于t的数字可能被放入不同的桶中。 但是,在这种情况下,它们只能在相邻的存储桶中。
以下是一个例子:
nums = [1, 5, 17, 6, 8], t = 2, k = 5
(我们现在不必担心k,因为它与nums的长度相同。)
由于t = 2,所以每当我们在列表中遇到一个数字时,我们将其除以t + 1(使用整数除法)并将其放入相应的桶中。
1 / 3 = 0 --> We put 1 in bucket[0]
5 / 3 = 1 --> We put 5 in bucket[1]
由于相邻存储桶[1]中的存储桶中可能存在满足要求的元素,因此我们需要检查。 bucket [0]的编号为1,但是5 - 1> 3还没有桶[2],所以我们继续。
17 / 3 = 5 --> We put 17 in bucket[5]
没有桶[4]或桶[6],所以我们继续。
6 / 3 = 2 --> We put 6 in bucket[2]
我们必须检查桶[1]中的数字:| 5 - 6 | = 1< 2所以有这样的数字,我们返回True。
如果我们继续将8放在桶[2]中,我们会发现其中已经有一个元素,即6个。由于一个桶中的所有元素的差异不大于t,我们就完成了。
因此,为了检查是否有两个差异小于t的数字,我们将我们遇到的每个数字都放在一个桶中。如果该存储桶中已有元素,那么我们就完成了。否则,我们检查相邻桶是否有满足要求的元素,如果没有,我们继续将数字放入桶中。
我们差不多完成了。现在我们需要考虑窗口宽度k。在将所有k个数字放入桶中后,如果我们没有找到两个这样的数字,我们需要将窗口向前移动一步。也就是说,删除最左边的号码及其对应的桶,并在其桶中添加新号码。如果已经采取了它的桶,那么我们就完成了。否则我们检查它的相邻桶,如果有的话。
下面是我的Python代码。
if t < 0 or not nums or k <= 0:
return False
buckets = {}
width = t + 1
for n, i in enumerate(nums):
buck = i // width
if buck in buckets:
return True
else:
buckets[buck] = i
if buck - 1 in buckets and i - buckets[buck-1] <= t:
return True
if buck + 1 in buckets and buckets[buck+1] - i <= t:
return True
if n >= k:
del buckets[nums[n-k] // width]
return False
答案 1 :(得分:1)
首先想到的实现只有两个for循环,一个嵌套。
在内部for循环中检查abs的逻辑(nums [i] -nums [j])&lt; = t和abs(i-j)&lt; = k。
外循环:i从0到n
内环:j从i + 1到n
答案 2 :(得分:0)
最优解O(n)可以按照@Jing Zhao的解释来完成。但是,在Java中,您必须注意其他事情,例如整数溢出。
这是我在Java中的解决方案:
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (nums == null || k < 0 || t < 0) return false;
Map<Integer, Integer> buckets = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int bucket = (int) Math.floorDiv(nums[i], (long) t + 1);
if (buckets.containsKey(bucket)) return true;
else {
buckets.put(bucket, nums[i]);
// Cast to long as it overflows if 2147483647 - (-1) => -2147483648
if (buckets.containsKey(bucket - 1) && nums[i] - (long) buckets.get(bucket - 1) <= t) return true;
if (buckets.containsKey(bucket + 1) && buckets.get(bucket + 1) - (long) nums[i] <= t) return true;
if (buckets.size() > k) {
buckets.remove((int) Math.floorDiv(nums[i - k], (long) t + 1));
}
}
}
return false;
}
首先,您必须处理负面的信息。例如,如果nums [i] = -1且t = 2:
int bucket0 = -1/3 = 0 // Remember to divide by (t+1)
这不行,因为它还会在存储桶“ 0”中放置另一个数字,例如nums [i] = 2,并且由于它们在同一个存储桶中,因此将返回true。但是-1和2之间的距离是3,而不是2!因此它应该返回false。解决此问题的正确方法是使用Math.floorDiv:
int bucket1 = Math.floorDiv(-1,3) = -1
这会将-1放入存储桶“ -1”中,并且应该可以正常工作。对于负数,存储桶会有所变化。这让我很难理解,因此我尝试更详细地解释它。
首先,我们除以(t + 1)而不仅仅是“ t”的原因如下: 假设t = 2,并且如果仅除以“ t”,则可以在存储桶“ 1”中:
2,3 // Because 2/2 and 3/2 give "1"
但是此存储桶仅占“ 1”而不是“ 2”的差,例如,还应包括4,因为4-2为2。现在,如果我们除以(t + 1):
3/3,4/3,5/3 // Because all those divisions yield "1"
有了这个,我们可以确定距离“ t”(在本例中为2)内的所有值都在同一存储桶中。
我们可以看到存储桶包含从精确除法开始的所有值,在本例中为3/3,下一个存储桶“ 2”将从6/3开始。但是在负数的情况下,它们将从精确除法中再增加一个数。这是因为存储桶0中包含0。
bucket with "1" => 3,4,5
bucket with "0" => 0,1,2
bucket with "-1" => -1,-2,-3
bucket with "-2" => -4,-5,-6
如果将Java中的常规int除法用于-1,我们将得到:
-1/3 = 0 // This would go to bucket "0"
-2/3 = 0 // This would go to bucket "0"
这是因为Java中的常规int除法只会截断,因此,如果您得到的值是0.xxxx,它将被截断为0,或者如果是-1.XXX,则将被截断为-1。 但是使用Math.floorDiv,除法将获得该值的实际下限,因此如果值为负-1.xxxx,则将得到-2,对于-1/3,则将为-1。
希望这可以帮助解决此问题的人。该解决方案非常聪明,您必须处理许多不同的情况。