LeetCode #532. K-diff Pairs in an Array 数组 哈希 双指针
Description
Given an array of integers and an integer k, you need to find the number of unique k-diff pairs in the array. Here a k-diff pair is defined as an integer pair (i, j), where i and j are both numbers in the array and their absolute difference is k.
Example 1:
Input: [3, 1, 4, 1, 5], k = 2
Output: 2
Explanation: There are two 2-diff pairs in the array, (1, 3) and (3, 5).
Although we have two 1s in the input, we should only return the number of unique pairs.
Example 2:
Input:[1, 2, 3, 4, 5], k = 1
Output: 4
Explanation: There are four 1-diff pairs in the array, (1, 2), (2, 3), (3, 4) and (4, 5).
Example 3:
Input: [1, 3, 1, 5, 4], k = 0
Output: 1
Explanation: There is one 0-diff pair in the array, (1, 1).
Note:
- The pairs (i, j) and (j, i) count as the same pair.
- The length of the array won't exceed 10,000.
- All the integers in the given input belong to the range: [-1e7, 1e7].
思路
解法一
这道题暴力解法很简单,将元素两两组合变成一个pair,并用一个数组存储所有的 pair 以便查重。当然,在暴力解法 O(n^3) 的基础上,我们可以利用 HashMap 把第三层的数组给优化成 Hash 查询,从而将时间复杂度优化为 O(n^2)。
Hash 的策略是这样:如果我们假设 pair 中的第一个元素不大于第二个元素,那么我们使用第一个元素就能唯一标识一个 pair。
当然,该 Hash 策略说明了我们可以只使用 HashSet,能比 HashMap 少用一些空间,因为 HashMap 中键值对的值并没有什么用。
特别需要注意的是,这道题有个很恶心的坑点,即 K 可以小于 0,而题目中说了 k 表示两数的绝对值,很容易误导人(我就是这么被误导了....)。
时间复杂度:O(n^2)
空间复杂度:O(n)
耗时 1696 ms, faster than 5%, Memory 17.7 MB
class Solution {
public:
int findPairs(const vector<int> &nums, int k) {
if (nums.size() < 2) return 0;
if (k < 0) return 0; // absolute difference won't be negative
int pair_num = 0;
// a number can represent a pair
// if the "first" of pair is always no more than the "second", like {2, 3}
unordered_set<int> visited;
for (int i = 0; i < nums.size() - 1; ++i) {
for (int j = i + 1; j < nums.size(); ++j) {
// Attention! k can be negative
if (abs(nums[i] - nums[j]) == k) {
if (nums[i] <= nums[j]) {
// num[i] is the "first" in pair
if (visited.find(nums[i]) == visited.end()) {
visited.insert(nums[i]);
++pair_num;
}
} else {
// num[j] is the "first" in pair
if (visited.find(nums[j]) == visited.end()) {
visited.insert(nums[j]);
++pair_num;
}
}
}
}
}
return pair_num;
}
};
解法二
更改一下 Hash 策略,key 还是各个数字,但是 value 变成了各个数字出现的次数。遍历一遍 nums 得到各个数字以及出现的次数后,可以遍历 map 中的所有键值对,使用 HashMap O(1) 的查找操作查找与当前间隔为 k 的数。
时间复杂度:O(n)
空间复杂度:O(n)
耗时 72 ms, faster than 30.7 %, Memory 18.4 MB
class Solution {
public:
int findPairs(const vector<int> &nums, int k) {
if (nums.size() < 2) return 0;
if (k < 0) return 0; // absolute difference won't be negative
int pair_num = 0;
// key is a number, value is frequency that the number appears
unordered_map<int, int> m;
for (int x : nums) {
auto iter = m.find(x);
if (iter != m.end()) {
++iter->second;
} else {
m[x] = 1;
}
}
for (const auto &item : m) {
if (k > 0) {
if (m.count(item.first + k)) ++pair_num;
} else if (k == 0) {
if (item.second > 1) ++pair_num;
}
}
return pair_num;
}
};
解法三
参考博客中还提到了一种使用双指针的解法,在本题里它是一个时间换空间的策略,因为必须先进行 O(nlgn) 的排序保证序列的有序性,然后再求绝对值,否则就变成了暴力解法了。
我们知道解法一中,对于每一个序列 {a, b} 且 a <= b,我们可以使用 a 来唯一表示 {a, b}。那么排序做法的另一个好处是我们不再需要用于 pair 查重时所需的 O(n) 容器,在每一轮中只需要一个变量存储 pair 即可。
具体解法可以参考一下文章末尾贴出的博客。
时间复杂度:O(n^2) = 排序O(nlgn) + 双重扫描O(n^2)
空间复杂度:O(n)