1055-值和下标之差都在给定的范围内

本文探讨了如何使用滑动窗口技巧配合有序集合(如set或unordered_map),解决给定整数数组中是否存在两个元素,它们的差的绝对值小于等于t,并且下标差小于等于k的问题。两种高效解决方案及其复杂度分析被详细解析。
摘要由CSDN通过智能技术生成

题目如下

给你一个整数数组 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

解题思路1

方法一:滑动窗口 + 有序集合
思路及算法

对于序列中每一个元素 x 左侧的至多 k 个元素,如果这 k 个元素中存在一个元素落在区间 [x−t,x+t] 中,我们就找到了一对符合条件的元素。注意到对于两个相邻的元素,它们各自的左侧的 k 个元素中有 k−1 个是重合的。于是我们可以使用滑动窗口的思路,维护一个大小为 k 的滑动窗口,每次遍历到元素 x 时,滑动窗口中包含元素 x 前面的最多 k 个元素,我们检查窗口中是否存在元素落在区间 [x−t,x+t] 中即可。

如果使用队列维护滑动窗口内的元素,由于元素是无序的,我们只能对于每个元素都遍历一次队列来检查是否有元素符合条件。如果数组的长度为 n,则使用队列的时间复杂度为 O(nk),会超出时间限制。

因此我们希望能够找到一个数据结构维护滑动窗口内的元素,该数据结构需要满足以下操作:

支持添加和删除指定元素的操作,否则我们无法维护滑动窗口;

内部元素有序,支持二分查找的操作,这样我们可以快速判断滑动窗口中是否存在元素满足条件,具体而言,对于元素 x,当我们希望判断滑动窗口中是否存在某个数 y 落在区间 [x−t,x+t] 中,只需要判断滑动窗口中所有大于等于 x−t 的元素中的最小元素是否小于等于x+t 即可。

我们可以使用有序集合来支持这些操作。

实现方面,我们在有序集合中查找大于等于 x−t 的最小的元素 yy,如果 y 存在,且 y≤x+t,我们就找到了一对符合条件的元素。完成检查后,我们将 x 插入到有序集合中,如果有序集合中元素数量超过了 k,我们将有序集合中最早被插入的元素删除即可。

注意
如果当前有序集合中存在相同元素,那么此时程序将直接返回 true。因此本题中的有序集合无需处理相同元素的情况。

为防止整型 int 溢出,我们既可以使用长整型 long,也可以对查找区间[x−t,x+t] 进行限制,使其落在 int 范围内。

class Solution
{
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t)
    {
        int n = nums.size();
        set<int> rec;
        for (int i = 0; i < n; i++) 
        {
            auto iter = rec.lower_bound(max(nums[i], INT_MIN + t) - t);
            if (iter != rec.end() && *iter <= min(nums[i], INT_MAX - t) + t) 
            {
                return true;
            }
            rec.insert(nums[i]);
            if (i >= k) 
            {
                rec.erase(nums[i - k]);
            }
        }
        return false;
    }
};

复杂度分析

时间复杂度:O(nlog(min(n,k))),其中 n 是给定数组的长度。每个元素至多被插入有序集合和从有序集合中删除一次,每次操作时间复杂度均为 O(log(min(n,k))。

空间复杂度:O(min(n,k)),其中 n 是给定数组的长度。有序集合中至多包含 min(n,k+1) 个元素。

解题思路2

方法二:桶
思路及算法

我们也可以使用利用桶排序的思想解决本题。我们按照元素的大小进行分桶,维护一个滑动窗口内的元素对应的元素。

对于元素 x,其影响的区间为[x−t,x+t]。于是我们可以设定桶的大小为 t+1。如果两个元素同属一个桶,那么这两个元素必然符合条件。如果两个元素属于相邻桶,那么我们需要校验这两个元素是否差值不超过 t。如果两个元素既不属于同一个桶,也不属于相邻桶,那么这两个元素必然不符合条件。

具体地,我们遍历该序列,假设当前遍历到元素 x,那么我们首先检查 x 所属于的桶是否已经存在元素,如果存在,那么我们就找到了一对符合条件的元素,否则我们继续检查两个相邻的桶内是否存在符合条件的元素。

实现方面,我们将 int 范围内的每一个整数 x 表示为 x=(t+1)×a+b(0≤b≤t) 的形式,这样 x 即归属于编号为 a 的桶。因为一个桶内至多只会有一个元素,所以我们使用哈希表实现即可。

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) 
    {
        unordered_map<int, int> mp;
        int n = nums.size();
        for (int i = 0; i < n; i++) 
        {
            long x = nums[i];
            int id = getID(x, t + 1ll);
            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) 
            {
                return true;
            }
            mp[id] = x;
            if (i >= k) 
            {
                mp.erase(getID(nums[i - k], t + 1ll));
            }
        }
        return false;
    }
};

复杂度分析

时间复杂度:O(n),其中 n 是给定数组的长度。每个元素至多被插入哈希表和从哈希表中删除一次,每次操作的时间复杂度均为 O(1)。

空间复杂度:O(min(n,k)),其中 n 是给定数组的长度。哈希表中至多包含 min(n,k+1) 个元素。

以下是一个基于回溯法的解法,可以求出所有满足要求的元素下标组合: ```c #include <stdio.h> #define ROW 3 #define COL 3 #define TARGET_SUM 10 void find_combination(int arr[][COL], int row, int col, int target_sum, int cur_sum, int cur_row, int cur_col, int visited[][COL], int* count, int* result[][COL]) { if (cur_sum == target_sum) { // 找到了一组组合,将其保存到结果集中 int i, j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (visited[i][j]) { result[*count][i][j] = 1; } } } (*count)++; return; } if (cur_row >= row) { // 行数超出范围,返回 return; } if (cur_col >= col) { // 列数超出范围,转到下一行继续查找 find_combination(arr, row, col, target_sum, cur_sum, cur_row+1, 0, visited, count, result); return; } if (cur_sum + arr[cur_row][cur_col] <= target_sum) { // 尝试选择当前元素 visited[cur_row][cur_col] = 1; find_combination(arr, row, col, target_sum, cur_sum+arr[cur_row][cur_col], cur_row, cur_col+1, visited, count, result); visited[cur_row][cur_col] = 0; } // 尝试不选择当前元素 find_combination(arr, row, col, target_sum, cur_sum, cur_row, cur_col+1, visited, count, result); } int main() { int arr[ROW][COL] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int visited[ROW][COL] = {{0}}; int result[ROW*COL][ROW][COL]; int count = 0; find_combination(arr, ROW, COL, TARGET_SUM, 0, 0, 0, visited, &count, result); int i, j, k; for (i = 0; i < count; i++) { printf("Combination %d:\n", i+1); for (j = 0; j < ROW; j++) { for (k = 0; k < COL; k++) { if (result[i][j][k]) { printf("(%d, %d) ", j, k); } } } printf("\n"); } return 0; } ``` 在上面的代码中,`find_combination`函数是用来查找元素下标组合的,其参数说明如下: - `arr`:原始的二维数组; - `row`:数组的行数; - `col`:数组的列数; - `target_sum`:需要求得的目标和; - `cur_sum`:当前已经求得的和; - `cur_row`:当前正在处理的行数; - `cur_col`:当前正在处理的列数; - `visited`:一个与原始数组大小相同的二维数组,用于记录已经访问过的元素; - `count`:一个指向整数的指针,用于记录找到的组合数量; - `result`:一个三维数组,用于保存找到的所有组合的下标信息。 在函数内部,我们首先判断当前是否已经求得目标和,如果是,则将当前的组合信息保存到 `result` 数组中,并将 `count` 加 1;否则,我们尝试选择当前元素,并递归地继续查找下一个元素;然后,我们再尝试不选择当前元素,并递归地查找下一个元素。 在 `main` 函数中,我们定义了一个原始数组 `arr`,一个记录访问情况的数组 `visited`,一个保存结果的三维数组 `result`,以及一个记录找到组合数量的变量 `count`。然后,我们调用 `find_combination` 函数来查找所有满足要求的组合,最后按顺序输出所有的组合信息即可。 注意:这个解法的时间复杂度是指数级的,对于大型数组的情况,可能会超时或者占用过多的内存,需要根据实际情况进行优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值