【C/C++】532. 数组中的 k-diff 数对

243 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情


题目链接:532. 数组中的 k-diff 数对

题目描述

给你一个整数数组 nums 和一个整数 k,请你在数组中找出 不同的 k-diff 数对,并返回不同的 k-diff 数对 的数目。

k-diff 数对定义为一个整数对 (nums[i], nums[j]) ,并满足下述全部条件:

  • 0i,j<nums.length0 \leqslant i, j < nums.length
  • i != j
  • nums[i] - nums[j] == k 注意,|val| 表示 val 的绝对值。

提示:

  • 1nums.length1041 \leqslant nums.length \leqslant 10^4
  • 107 nums[i]107-10^7 \leqslant nums[i] \leqslant 10^7
  • 0k1070 \leqslant k \leqslant 10^7

示例 1:

输入:nums = [3, 1, 4, 1, 5], k = 2
输出:2
解释:数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。
尽管数组中有两个 1 ,但我们只应返回不同的数对的数量。

示例 2:

输入:nums = [1, 2, 3, 4, 5], k = 1
输出:4
解释:数组中有四个 1-diff 数对, (1, 2), (2, 3), (3, 4) 和 (4, 5) 。

示例 3:

输入: nums = [1, 3, 1, 5, 4], k = 0
输出: 1
解释: 数组中只有一个 0-diff 数对,(1, 1) 。

整理题意

题目给定一个整数数组 nums 和一个整数 k,要求我们在数组 nums 中找到两个整数 nums[i]nums[j],使得两个整数差的绝对值为 k,问数组中存在多少个这样不同的数对。

需要注意的是 (nums[i], nums[j])(nums[j], nums[i]) 是属于同一对数对的。

解题思路分析

首先观察题目数据范围,nums[i] 存在负数,且范围在 ±107±10^7 以内,由于数组下标不存在负数,且无法开辟这么大空间的数组,所以只能采用 哈希表 进行存储。

方法一:set 集合

又因为题目要求返回 不同k-diff 数对数目,所以可以考虑采用 set 集合来实现去重的目的。同时因为 (nums[i], nums[j])(nums[j], nums[i]) 是属于同一对数对,同时 k 值为固定值,所以我们只需要存储一对数对中较小或者较大的一个数即可代表一个数对,那么在 set 集合中只需要存储一个整数来表示一个数对,同时还能达到自动去重的效果。

我们再利用一个哈希表来记录已经遍历过的整数,这样就可以在匹配数对的时候判断是否存在与当前整数凑成 k-diff 数对的整数,由于只需要判断元素是否存在,所以这里也可以使用 set 集合来存储。

但是需要注意每次需要先判断再放入 set 集合中,避免 k = 0 时自己与自己匹配的情况。

方法二:排序 + 双指针

因为题目只要求下标不等即可,对原数组进行排序后可以使用双指针进行查找 k-diff 数对。

具体实现

方法一:set 集合

  1. 创建两个 set 集合 sviss 用来存储数对中较小的一个整数表示一个数对,vis 用来统计已经遍历过的整数。
  2. 遍历数组 nums,每次判断 nums[i] + knums[i] - k 是否在已经遍历过的整数集合 vis 中;
  3. 如果在集合 vis 说明可以与之凑成 k-diff 对,将较小的一个放入 s 集合中表示一对 k-diff 数对。
  4. 最后返回 s 集合的大小即为不同的 k-diff 数对的数目。

方法二:排序 + 双指针

固定数对中一个整数,利用双指针寻找另一个整数,更具体的:

  1. 固定右端点寻找满足 k-diff 数对的左端点;
  2. 找到第一个差值 大于等于 k 的整数进行判断是否能够组成 k-diff 数对;
  3. 依次固定每个整数为右端点,找到满足 k-diff 数对的左端点;
  • 需要注意的是,由于左右指针只增不减,寻找 k-diff 数对的时间复杂度为 O(n)O(n)
  • ★ 另外需要特别 注意 k == 0 时的边界处理以及排除连续相同的数字重复计算。

复杂度分析

方法一:set 集合

  • 时间复杂度:其中 n 是数组 nums 的长度。需要遍历 nums 一次。unordered_set 查找和插入元素时间复杂度可以看作常数。
  • 空间复杂度:其中 n 是数组 nums 的长度。两个哈希表最多各存放 n 个元素。

方法二:排序 + 双指针

  • 时间复杂度:O(nlogn)O(n \log n),其中 n 是数组 nums 的长度。排序需要消耗 O(nlogn)O(n\log n) 复杂度,利用双指针寻找 k-diff 数对的时间复杂度为 O(n)O(n),总的时间复杂度为 O(nlogn)O(n\log n)
  • 空间复杂度:O(logn)O(\log n),其中 n 是数组 nums 的长度。排序消耗 O(logn)O(\log n) 复杂度,其余消耗常数空间复杂度,总的空间复杂度为 O(logn)O(\log n)

代码实现

方法一:set 集合

class Solution {
public:
    int findPairs(vector<int>& nums, int k) {
        //set可以完成去重的效果
        unordered_set<int> s, vis;
        //s记录构成k-diff数对中较小的那个数来代表这个数对
        s.clear();
        //vis记录集合中是否有与之匹配的数对
        vis.clear();
        int n = nums.size();
        //遍历数组nums
        for(int i = 0; i < n; i++){
            //先判断vis集合中是否有与之配对的k-diff数对
            if(vis.count(nums[i] - k)){
                //放入k-diff数对中较小的一个代表一个数对
                s.insert(nums[i] - k);
            }
            if(vis.count(nums[i] + k)){
                s.insert(nums[i]);
            }
            //判断完后再放入nums[i],避免 k = 0 的情况
            vis.insert(nums[i]);
        }
        //s集合中元素个数就代表了k-diff数对的个数
        return s.size();
    }
};

方法二:排序 + 双指针

class Solution {
public:
    int findPairs(vector<int>& nums, int k) {
        //对数组nums排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        //双指针在有序数组中寻找 k-diff 数对
        int l = 0, r = 1, ans = 0;
        //固定右端点寻找满足 k-diff 数对的左端点
        while(r < n){
            //nums[r] != nums[r + 1] 是排除连续相同的数字重复计算
            //r == n - 1 是为了计算第最后一个连续相同的数字成为 k-diff 数对(k = 0时)
            if(r == n - 1 || nums[r] != nums[r + 1]){
                //寻找左端点
                while(l < r && nums[l] + k < nums[r]) l++;
                //当左右端点不等时判断是否为 k-diff数对
                if(l != r && nums[l] + k == nums[r]) ans++;
            }
            //固定下一个右端点
            r++;
        }
        return ans;
    }
};

总结

  • 看见题目求 不同 数对时,或者遇到去重较为困难的时候,可以考虑使用 set 集合来达到去重的效果。
  • 题目求满足条件的 k-diff 数对,由于对下标的要求不高(仅需下标不同即可),我们可以考虑对数组排序,在有序数组中,我们可以优先考虑 二分双指针 来解决问题。
  • 测试结果:

532. 数组中的 k-diff 数对.png

532. 数组中的 k-diff 数对 双指针.png 理论上哈希表的时间复杂度较低,但由于常数的不稳定性,在实际测试中,排序+双指针的做法在时间复杂度和空间复杂度上更优。

结束语

每个人的生命里都有一段艰难的旅程,但是别怕,坚持走下去,你的人生终将变得美好而辽阔。不管遇到什么,请始终记得:不要停止奔跑,继续奔走在自己的热爱里;别辜负时光,去尽情拥抱属于自己的生活。