最近在规律的刷题。写一下做题的随想,仅做记录,欢迎指正。₍˄·͈༝·͈˄₎◞ ̑̑*
1. 题目
2. 我的题解
// 使用桶排序 + 滑动窗口的思想
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
// 依然是经典的failFast
if (nums.length <= 1 || indexDiff < 0 || valueDiff < 0) {
return false;
}
// 设定桶的大小为valueDiff+1,这样的做可以保障每个桶里面的元素只差绝对值不会大于valueDiff
int bucketSize=valueDiff+1;
// 桶的编号——桶中的元素 映射关系
Map<Integer, Integer> buckets = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int bucketId = getBucketId(nums[i], bucketSize);
// 情况1:当我们计算得出的桶编号对应的桶中已经存在了元素,那么该元素与当前元素只差必定小于ValueDiff,符合题意直接返回
// 难道不用计算IndexDiff了吗?请继续往下看 哈哈
if (buckets.containsKey(bucketId)) {
return true;
}
// 情况2:如果情况1不存在,计算当前桶左边的桶中的元素是不是满足与当前元素之差的绝对值小于ValueDiff的情况
if (buckets.get(bucketId - 1) != null && Math.abs(buckets.get(bucketId - 1) - nums[i]) <= valueDiff) {
return true;
}
// 情况3:计算右边的桶中元素是否满足条件
if (buckets.get(bucketId + 1) != null && Math.abs(buckets.get(bucketId + 1) - nums[i]) <= valueDiff) {
return true;
}
// 如果上述3种情况都不满足,那我们将当前元素放到桶中,继续下一轮尝试
buckets.put(bucketId,nums[i]);
// 关键条件来了:这里是一个滑动窗口的思想。
// 因为我们是从索引0开始for循环的,所以当下标i超多indexDiff时,我们需要将滑动窗口左边的元素从桶中移除掉。
// 这样桶中这样剩下来的所有元素的索引与当前循环的索引i之差的绝对值必定满足 indexDiff。
if (i >= indexDiff) {
buckets.remove(getBucketId(nums[i - indexDiff],bucketSize));
}
}
return false;
}
private int getBucketId(int numValue, int bucketSize) {
// 这里需要考虑在数组中存在负数的情况,比较绕。
return numValue < 0 ? (numValue + 1) / bucketSize - 1 : numValue / bucketSize;
}
}
3. 思考
3.1. 第一发应
使用滑动窗口来解答。
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
int length = nums.length;
if (length <= 1) {
return false;
}
// 滑动窗口遍历
int end = 1;
while (end < length) {
for (int i = 0; i < end; i++) {
int valueDelta = Math.abs(nums[end] - nums[i]);
int indexDelta = end - i;
if (indexDelta > indexDiff) {
continue;
}
if (valueDelta > valueDiff) {
continue;
}
return true;
}
end++;
}
return false;
}
}
可以看出直接使用滑动窗口去解答会导致超长的测试用例输入进来时,直接运行超时。
我们来分析一下时间复杂度:
- 外层的while循环总共会执行n次(最坏情况)
- 随着end的增加,内层的滑动窗口依次会执行 1,2,3……n次(最坏情况)
- 那么总的次数是等差数列之和:(n+1)*n/2
- 最终得出时间复杂度为O(n^2)
3.2. 更多思考
在使用【桶排序+滑动窗口】来解答时,不难看出时间复杂度为O(n)。但是有个比较难受的地方就是桶排序时遇到负数就需要特殊处理。那怎么更直观的解决这个问题呢?我们可以先找到这个数组中最小的负数min,然后将数组中的元素依次减去min,这样全数组就都是非负数了呀!
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
if (nums.length <= 1 || indexDiff <= 0 || valueDiff < 0) {
return false;
}
// 找出数组中的最小值
int minValue = Integer.MAX_VALUE;
for (int num : nums) {
minValue = Math.min(minValue, num);
}
// 创建桶大小 (bucketSize)
int bucketSize = valueDiff + 1;
// 创建桶映射
Map<Integer, Integer> buckets = new HashMap<>();
// 遍历每个数字
for (int i = 0; i < nums.length; i++) {
// 对每个数字进行平移,使所有元素都变为非负
int newNum = nums[i] - minValue;
// 获取桶编号
int bucketId = getBucketId(newNum, bucketSize);
// 检查当前桶中是否已经有元素
if (buckets.containsKey(bucketId)) {
return true;
}
// 检查相邻桶(左边和右边)
if (buckets.containsKey(bucketId - 1) && Math.abs(buckets.get(bucketId - 1) - newNum) <= valueDiff) {
return true;
}
if (buckets.containsKey(bucketId + 1) && Math.abs(buckets.get(bucketId + 1) - newNum) <= valueDiff) {
return true;
}
// 将当前元素放入桶中
buckets.put(bucketId, newNum);
// 如果超出 indexDiff 的范围,则移除旧的元素
if (i >= indexDiff) {
int oldBucketId = getBucketId(nums[i - indexDiff] - minValue, bucketSize);
buckets.remove(oldBucketId);
}
}
return false;
}
// 获取桶编号
private int getBucketId(int numValue, int bucketSize) {
return numValue / bucketSize;
}
}
好了,今天就到这里吧。祝各位晚安₍˄·͈༝·͈˄*₎◞ ̑̑**