K-diff Pairs in an Array
MedPrompt
Given an array of integers nums and an integer k, return the number of unique k-diff pairs in the array.
A k-diff pair is an integer pair (nums[i], nums[j]), where the following are true:
0 <= i, j < nums.lengthi != j|nums[i] - nums[j]| == k
Notice that |val| denotes the absolute value of val.
Example 1:
Input: nums = [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: nums = [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: nums = [1,3,1,5,4], k = 0
Output: 1
Explanation: There is one 0-diff pair in the array, (1, 1).
Constraints:
1 <= nums.length <= 104-107 <= nums[i] <= 1070 <= k <= 107
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through all possible pairs of elements in the array and checks if their absolute difference is equal to k. To ensure that we only count unique pairs, a Set is used to store the pairs found in a canonical form.
Algorithm
- Initialize an empty
HashSet<String>calleduniquePairsto store the unique pairs. - Iterate through the array with a pointer
ifrom0ton-1. - Inside this loop, iterate with another pointer
jfromi+1ton-1. - For each pair
(nums[i], nums[j]), check ifMath.abs(nums[i] - nums[j]) == k. - If the condition is true, form a canonical string representation of the pair, e.g.,
Math.min(nums[i], nums[j]) + "," + Math.max(nums[i], nums[j]), and add it to theuniquePairsset. - After the loops complete, return the size of
uniquePairs.
Walkthrough
We can solve this problem by checking every possible pair of numbers in the array. We use two nested loops to generate these pairs.
For each pair (nums[i], nums[j]) where i < j, we calculate the absolute difference |nums[i] - nums[j]|.
If the difference equals k, we have found a k-diff pair. However, the problem asks for unique pairs. For example, in [1, 3, 1, 3] with k=2, the pair (1, 3) should be counted only once.
To handle this, we store the found pairs in a Set. To treat (a, b) and (b, a) as the same pair, we store them in a canonical order, for instance, (min(a, b), max(a, b)). A Set of Pair objects or a Set of String representations like "min_val,max_val" can be used.
The final result is the size of this set.
import java.util.HashSet;import java.util.Set; class Solution { public int findPairs(int[] nums, int k) { if (k < 0) { return 0; } Set<String> uniquePairs = new HashSet<>(); for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { if (Math.abs(nums[i] - nums[j]) == k) { int min = Math.min(nums[i], nums[j]); int max = Math.max(nums[i], nums[j]); uniquePairs.add(min + "," + max); } } } return uniquePairs.size(); }}Complexity
Time
O(N^2), where N is the number of elements in `nums`. The two nested loops result in a quadratic time complexity as we compare every element with every other element.
Space
O(P), where P is the number of unique pairs. In the worst-case scenario, P can be up to N, so the space complexity is O(N).
Trade-offs
Pros
Simple to understand and implement.
Cons
Inefficient for large input arrays due to its O(N^2) time complexity, which will likely result in a 'Time Limit Exceeded' error on most platforms.
A more efficient approach involves sorting the array first. Once sorted, we can use a two-pointer technique to find the pairs in linear time. This method avoids redundant comparisons and handles duplicates effectively.
Algorithm
- Sort the input array
nums. - Initialize
count = 0,left = 0, andright = 1. - Loop while
right < nums.length. - If
left == rightornums[right] - nums[left] < k, incrementright. - Else if
nums[right] - nums[left] > k, incrementleft. - Else (a pair is found):
- Increment
count. - Increment
left. - Skip any duplicates for the
leftpointer by advancing it whilenums[left] == nums[left - 1]. - Move
rightto be at leastleft + 1.
- Increment
- Return
count.
Walkthrough
This approach leverages the fact that if the array is sorted, we can search for pairs more efficiently.
First, we sort the input array nums.
We then use two pointers, left and right, initialized to 0 and 1 respectively.
We iterate while right is within the bounds of the array:
- Calculate the difference
diff = nums[right] - nums[left]. - If
diff == k, we've found a unique pair. We increment our count. To avoid counting duplicates, we then advance theleftpointer past any subsequent identical elements. - If
diff < k, the difference is too small. We need to increase it, so we move therightpointer to the right (right++). - If
diff > k, the difference is too large. We need to decrease it, so we move theleftpointer to the right (left++).
We must also handle the case where left and right pointers meet, by incrementing right to ensure left != right.
This two-pointer scan on the sorted array correctly identifies unique pairs without needing an extra set for the pairs themselves, as the duplicate handling is managed by advancing the pointers past identical elements.
import java.util.Arrays; class Solution { public int findPairs(int[] nums, int k) { Arrays.sort(nums); int count = 0; int left = 0; for (int right = 1; right < nums.length; right++) { // Skip duplicates for the right pointer if (right < nums.length - 1 && nums[right] == nums[right + 1]) { continue; } // Skip duplicates for the left pointer while (left < right && nums[left] == nums[left + 1]) { left++; } while (left < right && nums[right] - nums[left] > k) { left++; } if (left < right && nums[right] - nums[left] == k) { count++; } } return count; }}An alternative, perhaps simpler two-pointer implementation:
import java.util.Arrays; class Solution { public int findPairs(int[] nums, int k) { Arrays.sort(nums); int count = 0; int left = 0; int right = 1; while (left < nums.length && right < nums.length) { if (left == right || nums[right] - nums[left] < k) { right++; } else if (nums[right] - nums[left] > k) { left++; } else { count++; left++; while (left < nums.length && nums[left] == nums[left - 1]) { left++; } right = Math.max(right + 1, left + 1); } } return count; }}Complexity
Time
O(N log N), dominated by the initial sorting of the array. The two-pointer scan takes O(N) time as each pointer traverses the array at most once.
Space
O(log N) or O(N), depending on the implementation of the sorting algorithm used. For example, in Java, `Arrays.sort()` for primitives uses a variant of Quicksort which has O(log N) space complexity on average.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Handles duplicates elegantly after sorting.
Cons
The sorting step makes it slower than a hash-based approach.
The logic for handling duplicates and moving pointers can be tricky to get right.
The most optimal approach uses a hash map or a hash set to achieve linear time complexity. By storing elements and their frequencies (or just their presence), we can quickly check for the existence of the other element in a potential pair.
Algorithm
- If
k < 0, return 0. - If
k == 0:- Create a
HashMapto store frequencies of numbers innums. - Iterate through the map's values. For each frequency greater than 1, increment a counter.
- Return the counter.
- Create a
- If
k > 0:- Create a
HashSetand add all elements fromnumsto it to get unique numbers. - Initialize a counter to 0.
- Iterate through each number
numin theHashSet. - Check if the set contains
num + k. If it does, increment the counter. - Return the counter.
- Create a
Walkthrough
This approach handles the problem in O(N) time by using a hash-based data structure. The logic is slightly different for k=0 and k>0.
Case 1: k > 0
We want to find pairs (x, y) where y = x + k. To count unique pairs, we can first find all unique numbers in the input array. A HashSet is perfect for this.
- Add all elements from
numsinto aHashSetto get rid of duplicates. - Initialize a
countto 0. - Iterate through each number
numin the hash set. - For each
num, check if the set also containsnum + k. - If it does, we have found a unique pair, so we increment
count. By iterating through the unique numbers and only checking fornum + k, we avoid double-counting.
Case 2: k = 0
We are looking for pairs (x, x), which means we need to find numbers that appear more than once in the array.
- Use a
HashMapto store the frequency of each number innums. - Initialize a
countto 0. - Iterate through the entries in the frequency map.
- If a number's frequency is greater than 1, it can form a 0-diff pair. Increment
count.
Finally, return the total count.
import java.util.HashMap;import java.util.Map;import java.util.HashSet;import java.util.Set; class Solution { public int findPairs(int[] nums, int k) { if (k < 0) { return 0; } int count = 0; if (k == 0) { Map<Integer, Integer> freqMap = new HashMap<>(); for (int num : nums) { freqMap.put(num, freqMap.getOrDefault(num, 0) + 1); } for (int freq : freqMap.values()) { if (freq > 1) { count++; } } } else { // k > 0 Set<Integer> uniqueNums = new HashSet<>(); for (int num : nums) { uniqueNums.add(num); } for (int num : uniqueNums) { if (uniqueNums.contains(num + k)) { count++; } } } return count; }}Complexity
Time
O(N), where N is the number of elements in `nums`. In both cases (`k=0` and `k>0`), we iterate through the array once to populate the hash map/set, and then iterate through the unique elements (at most N), with each operation being O(1) on average.
Space
O(N) in the worst case, where all elements are unique. The hash map or hash set needs to store up to N distinct elements.
Trade-offs
Pros
Most efficient time complexity at O(N).
The logic is straightforward and easy to reason about.
Cons
Requires extra space to store the hash map or hash set, which could be a concern for very large inputs with many unique elements.
Solutions
Solution
class Solution {public int findPairs(int[] nums, int k) { Set<Integer> vis = new HashSet<>(); Set<Integer> ans = new HashSet<>(); for (int v : nums) { if (vis.contains(v - k)) { ans.add(v - k); } if (vis.contains(v + k)) { ans.add(v); } vis.add(v); } return ans.size(); }}class Solution: def findPairs(self, nums: List[int], k: int) -> int: vis, ans = set(), set() for v in nums: if v - k in vis: ans . add(v - k) if v + k in vis: ans . add(v) vis . add(v) return len(ans)class Solution {public: int findPairs(vector<int> &nums, int k) { unordered_set<int> vis; unordered_set<int> ans; for (int &v : nums) { if (vis.count(v - k)) ans.insert(v - k); if (vis.count(v + k)) ans.insert(v); vis.insert(v); } return ans.size(); }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.