给你一个整数数组 nums 和两个整数 k 和 t 。
请你判断是否存在 两个不同下标 i 和 j,
使得 abs(nums[i] - nums[j]) <= t 同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
示例 2:
输入:nums = [1,0,1,1], k = 1, t = 2
输出:true
示例 3:
输入:nums = [1,5,9,1,5,9], k = 2, t = 3
输出:false
提示:
0 <= nums.length <= 2 * 10^4
-2^31 <= nums[i] <= 2^31 - 1
0 <= k <= 10^4
0 <= t <= 2^31 - 1
法一: 暴力查找(超时)
bool containsNearbyAlmostDuplicate(int* nums, int numsSize, int k, int t){
int i, j;
for (i = 0; i < numsSize; i++) {
for (j = i + 1; abs(i - j) <= k && j < numsSize; j++) {
if (abs((long)nums[i] - nums[j]) <= t) {
return true;
}
}
}
return false;
}
法二:排序+模拟哈希
struct numsInfo
{
int num;
int index;
};
int compare (const void* e1,const void* e2)
{
//不能return ((struct numsInfo*)e1)->num - ((struct numsInfo*)e2)
//有可能会溢出
//就用下面的类型判断
return ((struct numsInfo*)e1)->num > ((struct numsInfo*)e2);
}
bool containsNearbyAlmostDuplicate(int* nums, int numsSize, int k, int t)
{
struct numsInfo* info = (struct numsInfo*)calloc(numsSize,sizeof(struct numsInfo));
for (int i = 0; i < numsSize; i++)
{
info[i].num = nums[i];
info[i].index = i;
}
qsort(info,numsSize,sizeof(struct numsInfo),compare);
//以 nums = [1,5,9,1,5,9]为例
//k = 2, t = 3
// 0 1 2 3 4 5
//qsort之后
// 1,1,5,5,9,9
// 0,3,1,4,2,5
int i = 0;
int j = 0;
for (i = 0; i < numsSize; i++)
{
for ( j = i + 1;j < numsSize; j++)
{
if ((double)info[j].num - info[i].num > t)
//结构体数组中的num由于qsort都按照从小到大顺序排列好了
//首先类型升
//(long)也行
break;
if (abs(info[j].index - info[i].index) <= k)
//按照题干给的要求来判断呗
return true;
}
}
return false;
}
时间复杂度:O(N^2)
空间复杂度:O(N)
法三:滑动窗口 + 有序集合
思路:
有序集合这个数据结构,内部有序,支持二分查找,减少时间复杂度
支持添加和删除数据操作,可以维护滑动窗口
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
set<int> s;
for (int i = 0; i < n; ++i){
auto iter = s.lower_bound(max(nums[i], INT_MIN + t) - t);
//使用max,min来控制数值范围在int类型内
if (iter != s.end() && *iter <= min(nums[i], INT_MAX - t) + t){
return true;
}
s.insert(nums[i]);
if (i >= k){//滑动窗口
s.erase(nums[i - k]);
}
}
return false;
}
};
很多小伙伴对getID有点迷惑,就是为什么取负数,
如w=10, 因为非负数是0~9,10~19...这种一组,
而负数是-1~-10, -11~-20...这些是一组,
如果-1~-10直接除以10,会被分到两组中,而不是-1这一组,
所以先+1变成-0--9,与正数一致,再除以10,最后减1,
正好是-1这一组,其它组也是同理
向上取整 不管四舍五入的规则 只要后面有小数前面的整数就加1
向下取整 不管四舍五入的规则 只要后面有小数忽略小数
class Solution {
public:
int getID(int x, long w){
return x < 0 ? (x + 1ll) / w - 1 : x / w;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
unordered_map<int, int> mp;
for (int i = 0; i < n; ++i){
long x = nums[i];
int id = getID(x, t + 1ll);
//[x - t, x + t],我们考虑id - 1, id, id + 1三个桶所以桶的大小可以选择 t + 1
if (mp.count(id)){
return true;
}
if (mp.count(id - 1) && abs(x - mp[id - 1]) <= t){
return true;
}
if (mp.count(id + 1) && abs(x - mp[id + 1]) <= t){//上述x不用long会溢出
//[2147483647,-1,2147483647] k = 1 t = 2147483647为例
return true;
}
mp[id] = x;
if (i >= k){
mp.erase(getID(nums[i - k], t + 1ll));//删除下标范围不在目标范围的桶
}
}
return false;
}
};