2341. Maximum Number of Pairs in Array
Problem Description
You are given a 0-indexed integer array nums
. Your task is to repeatedly perform an operation that removes pairs of equal integers from the array.
In each operation, you:
- Choose two integers in
nums
that are equal - Remove both integers from
nums
, forming a pair
You continue performing this operation as many times as possible until no more pairs can be formed.
Return a 0-indexed integer array answer
of size 2 where:
answer[0]
is the total number of pairs that are formedanswer[1]
is the number of leftover integers remaining innums
after all possible pairs have been removed
For example, if nums = [1, 3, 2, 1, 3, 2, 2]
, you can form:
- One pair of 1's (removing two 1's)
- One pair of 3's (removing two 3's)
- One pair of 2's (removing two 2's)
This gives you 3 pairs total, with 1 leftover integer (the remaining 2), so the answer would be [3, 1]
.
The solution counts the frequency of each number in the array. For each number that appears v
times, you can form v // 2
pairs (integer division). The total number of pairs is the sum of v // 2
for all distinct numbers. The leftover count is simply the original array length minus twice the number of pairs formed.
Intuition
The key insight is that we need to count how many pairs we can form from equal elements. Think about it this way: if a number appears multiple times in the array, we can only form pairs from those occurrences.
For instance, if the number 5
appears 7 times, we can form 7 // 2 = 3
pairs, leaving 1 occurrence of 5
unpaired. This is because each pair consumes exactly 2 equal numbers.
This naturally leads us to a frequency counting approach:
- First, count how many times each unique number appears in the array
- For each unique number with frequency
v
, we can form exactlyv // 2
pairs - The total pairs is the sum of all individual pair counts
The elegance of this approach is that we don't need to actually perform the removal operations. We can directly calculate the final result mathematically:
- Total pairs = sum of
(frequency // 2)
for all unique numbers - Leftover elements = original array length - (total pairs × 2)
The multiplication by 2 in the leftover calculation makes sense because each pair removes exactly 2 elements from the original array.
This transforms what could be a complex simulation problem into a simple counting problem that can be solved in linear time with a single pass through the frequency counts.
Solution Approach
The implementation uses a counting strategy with a hash table to efficiently solve the problem.
Step 1: Count Frequencies
cnt = Counter(nums)
We use Python's Counter
from the collections module to create a frequency map. This gives us a dictionary where keys are the unique numbers from nums
and values are their occurrence counts.
Step 2: Calculate Total Pairs
s = sum(v // 2 for v in cnt.values())
We iterate through all frequency values in the counter. For each frequency v
:
v // 2
gives us the number of pairs we can form from that specific number- We sum all these individual pair counts to get the total number of pairs
s
For example, if cnt = {1: 5, 2: 3, 4: 6}
:
- Number 1 appears 5 times → can form
5 // 2 = 2
pairs - Number 2 appears 3 times → can form
3 // 2 = 1
pair - Number 4 appears 6 times → can form
6 // 2 = 3
pairs - Total pairs
s = 2 + 1 + 3 = 6
Step 3: Calculate Leftover Elements
return [s, len(nums) - s * 2]
s * 2
represents the total number of elements used to form pairs (each pair uses 2 elements)len(nums) - s * 2
gives us the count of leftover elements that couldn't form pairs
The time complexity is O(n)
where n
is the length of the input array, as we iterate through the array once to build the counter and then iterate through the unique values once more. The space complexity is O(k)
where k
is the number of unique elements in the array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 3, 2, 1, 3, 2, 2]
.
Step 1: Count Frequencies
First, we count how many times each number appears:
- Number 1 appears 2 times
- Number 2 appears 3 times
- Number 3 appears 2 times
So our frequency map is: {1: 2, 2: 3, 3: 2}
Step 2: Calculate Pairs for Each Number
For each unique number, we determine how many pairs we can form:
- Number 1 with frequency 2:
2 // 2 = 1
pair - Number 2 with frequency 3:
3 // 2 = 1
pair (one 2 will be left over) - Number 3 with frequency 2:
2 // 2 = 1
pair
Total pairs: 1 + 1 + 1 = 3
Step 3: Calculate Leftover Elements
We started with 7 elements in total (len(nums) = 7
).
We formed 3 pairs, which used 3 × 2 = 6
elements.
Leftover elements: 7 - 6 = 1
Final Answer: [3, 1]
This matches our expectation - we formed 3 pairs (one pair each of 1's, 2's, and 3's), and we have 1 leftover element (the third occurrence of 2 that couldn't form a pair).
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def numberOfPairs(self, nums: List[int]) -> List[int]:
6 # Count the frequency of each number in the array
7 frequency_count = Counter(nums)
8
9 # Calculate the total number of pairs that can be formed
10 # Each element with frequency v can form v // 2 pairs
11 total_pairs = sum(freq // 2 for freq in frequency_count.values())
12
13 # Calculate the number of leftover elements
14 # Total elements minus (pairs * 2) gives us the remaining elements
15 leftover_elements = len(nums) - total_pairs * 2
16
17 # Return the number of pairs and leftover elements
18 return [total_pairs, leftover_elements]
19
1class Solution {
2 public int[] numberOfPairs(int[] nums) {
3 // Create a frequency array to count occurrences of each number (0-100)
4 int[] frequency = new int[101];
5
6 // Count the frequency of each number in the input array
7 for (int number : nums) {
8 frequency[number]++;
9 }
10
11 // Calculate the total number of pairs that can be formed
12 int totalPairs = 0;
13 for (int count : frequency) {
14 // Each number can form count/2 pairs
15 totalPairs += count / 2;
16 }
17
18 // Calculate leftover elements that cannot form pairs
19 int leftoverElements = nums.length - (totalPairs * 2);
20
21 // Return array with [number of pairs, number of leftover elements]
22 return new int[] {totalPairs, leftoverElements};
23 }
24}
25
1class Solution {
2public:
3 vector<int> numberOfPairs(vector<int>& nums) {
4 // Create a frequency array to count occurrences of each number (0-100)
5 vector<int> frequency(101);
6
7 // Count the frequency of each number in the input array
8 for (int& num : nums) {
9 ++frequency[num];
10 }
11
12 // Calculate the total number of pairs that can be formed
13 int totalPairs = 0;
14 for (int& count : frequency) {
15 // Each pair requires 2 elements, so divide count by 2
16 totalPairs += count >> 1; // Equivalent to count / 2
17 }
18
19 // Calculate leftover elements that cannot form pairs
20 int leftoverElements = static_cast<int>(nums.size()) - totalPairs * 2;
21
22 // Return the number of pairs and leftover elements
23 return {totalPairs, leftoverElements};
24 }
25};
26
1/**
2 * Counts the number of pairs that can be formed from the array and remaining elements
3 * @param nums - Array of integers to process
4 * @returns Array with [number of pairs, number of leftover elements]
5 */
6function numberOfPairs(nums: number[]): number[] {
7 // Store the total length of input array
8 const totalElements: number = nums.length;
9
10 // Initialize frequency array to count occurrences of each number (0-100)
11 const frequencyCount: number[] = new Array(101).fill(0);
12
13 // Count the frequency of each number in the input array
14 for (const num of nums) {
15 frequencyCount[num]++;
16 }
17
18 // Calculate total number of pairs by dividing each frequency by 2 (using bit shift)
19 // Right shift by 1 is equivalent to integer division by 2
20 const totalPairs: number = frequencyCount.reduce((accumulator: number, frequency: number) => {
21 return accumulator + (frequency >> 1);
22 }, 0);
23
24 // Calculate leftover elements: total elements minus elements used in pairs
25 const leftoverElements: number = totalElements - (totalPairs * 2);
26
27 return [totalPairs, leftoverElements];
28}
29
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because:
- Creating the Counter requires iterating through all
n
elements once:O(n)
- Iterating through the Counter values to calculate the sum takes
O(C)
time, whereC
is the number of unique elements - Since
C ≤ n
, the overall time complexity isO(n)
The space complexity is O(C)
, where C
is the number of unique elements in nums
. In this problem, since the range of numbers is limited to 101
possible values (as mentioned in the reference), the space complexity is effectively O(101) = O(1)
in the worst case. The Counter dictionary stores at most C
key-value pairs, where C
represents the distinct integers in the input array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Attempting to Modify the Array While Iterating
A common mistake is trying to remove pairs directly from the original array while iterating through it:
# INCORRECT APPROACH
def numberOfPairs(self, nums: List[int]) -> List[int]:
pairs = 0
i = 0
while i < len(nums):
j = i + 1
while j < len(nums):
if nums[i] == nums[j]:
nums.pop(j) # Removing elements while iterating
nums.pop(i) # This causes index shifting issues
pairs += 1
break
j += 1
else:
i += 1
return [pairs, len(nums)]
Why it's problematic:
- Modifying a list while iterating causes index shifting, leading to skipped elements or index out of bounds errors
- Time complexity becomes O(n³) due to the nested loops and list removal operations
- The logic becomes complex and error-prone
Solution: Use the frequency counting approach instead of modifying the original array.
Pitfall 2: Incorrect Leftover Calculation
Some might incorrectly calculate leftovers using modulo operations on individual frequencies:
# INCORRECT APPROACH
def numberOfPairs(self, nums: List[int]) -> List[int]:
cnt = Counter(nums)
pairs = sum(v // 2 for v in cnt.values())
leftovers = sum(v % 2 for v in cnt.values()) # Wrong calculation
return [pairs, leftovers]
Why it's problematic:
v % 2
only gives 0 or 1 for each unique number, not the actual count of leftover elements- For example, if a number appears 5 times,
5 % 2 = 1
, but we actually have 1 leftover element of that specific number
Solution: Calculate leftovers as len(nums) - pairs * 2
, which correctly accounts for the total elements removed.
Pitfall 3: Using Nested Loops for Pair Finding
Implementing a brute force O(n²) solution with nested loops:
# INEFFICIENT APPROACH
def numberOfPairs(self, nums: List[int]) -> List[int]:
used = [False] * len(nums)
pairs = 0
for i in range(len(nums)):
if used[i]:
continue
for j in range(i + 1, len(nums)):
if not used[j] and nums[i] == nums[j]:
used[i] = used[j] = True
pairs += 1
break
leftovers = sum(1 for u in used if not u)
return [pairs, leftovers]
Why it's problematic:
- O(n²) time complexity is unnecessarily slow for large inputs
- More complex logic with boolean tracking arrays
- Higher chance of implementation errors
Solution: Use the O(n) frequency counting approach which is both faster and simpler to implement.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!