持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
题目链接:532. 数组中的 k-diff 数对
题目描述
给你一个整数数组 nums 和一个整数 k,请你在数组中找出 不同的 k-diff 数对,并返回不同的 k-diff 数对 的数目。
k-diff 数对定义为一个整数对 (nums[i], nums[j]) ,并满足下述全部条件:
i != jnums[i] - nums[j] == k注意,|val|表示val的绝对值。
提示:
示例 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] 存在负数,且范围在 以内,由于数组下标不存在负数,且无法开辟这么大空间的数组,所以只能采用 哈希表 进行存储。
方法一:set 集合
又因为题目要求返回 不同 的 k-diff 数对数目,所以可以考虑采用 set 集合来实现去重的目的。同时因为 (nums[i], nums[j]) 和 (nums[j], nums[i]) 是属于同一对数对,同时 k 值为固定值,所以我们只需要存储一对数对中较小或者较大的一个数即可代表一个数对,那么在 set 集合中只需要存储一个整数来表示一个数对,同时还能达到自动去重的效果。
我们再利用一个哈希表来记录已经遍历过的整数,这样就可以在匹配数对的时候判断是否存在与当前整数凑成 k-diff 数对的整数,由于只需要判断元素是否存在,所以这里也可以使用 set 集合来存储。
但是需要注意每次需要先判断再放入
set集合中,避免k = 0时自己与自己匹配的情况。
方法二:排序 + 双指针
因为题目只要求下标不等即可,对原数组进行排序后可以使用双指针进行查找 k-diff 数对。
具体实现
方法一:set 集合
- 创建两个
set集合s和vis,s用来存储数对中较小的一个整数表示一个数对,vis用来统计已经遍历过的整数。 - 遍历数组
nums,每次判断nums[i] + k和nums[i] - k是否在已经遍历过的整数集合vis中; - 如果在集合
vis说明可以与之凑成k-diff对,将较小的一个放入s集合中表示一对k-diff数对。 - 最后返回
s集合的大小即为不同的k-diff数对的数目。
方法二:排序 + 双指针
固定数对中一个整数,利用双指针寻找另一个整数,更具体的:
- 固定右端点寻找满足
k-diff数对的左端点; - 找到第一个差值 大于等于
k的整数进行判断是否能够组成k-diff数对; - 依次固定每个整数为右端点,找到满足
k-diff数对的左端点;
- 需要注意的是,由于左右指针只增不减,寻找
k-diff数对的时间复杂度为 。- ★ 另外需要特别 注意
k == 0时的边界处理以及排除连续相同的数字重复计算。
复杂度分析
方法一:set 集合
- 时间复杂度:其中
n是数组nums的长度。需要遍历nums一次。unordered_set查找和插入元素时间复杂度可以看作常数。 - 空间复杂度:其中
n是数组nums的长度。两个哈希表最多各存放n个元素。
方法二:排序 + 双指针
- 时间复杂度:,其中
n是数组nums的长度。排序需要消耗 复杂度,利用双指针寻找k-diff数对的时间复杂度为 ,总的时间复杂度为 。 - 空间复杂度:,其中
n是数组nums的长度。排序消耗 复杂度,其余消耗常数空间复杂度,总的空间复杂度为 。
代码实现
方法一: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数对,由于对下标的要求不高(仅需下标不同即可),我们可以考虑对数组排序,在有序数组中,我们可以优先考虑 二分 和 双指针 来解决问题。 - 测试结果:
理论上哈希表的时间复杂度较低,但由于常数的不稳定性,在实际测试中,排序+双指针的做法在时间复杂度和空间复杂度上更优。
结束语
每个人的生命里都有一段艰难的旅程,但是别怕,坚持走下去,你的人生终将变得美好而辽阔。不管遇到什么,请始终记得:不要停止奔跑,继续奔走在自己的热爱里;别辜负时光,去尽情拥抱属于自己的生活。