2206. Divide Array Into Equal Pairs

In this engineering deep dive, we solve LeetCode 2206: Divide Array Into Equal Pairs. We explore frequency parity mechanics, evaluate memory-efficient counting arrays in modern C++ versus hash set toggling in TypeScript, and analyze bitwise optimization patterns in high-throughput data processing pipelines.

Problem Statement & Formal Constraints

You are given an integer array nums consisting of 2 * n integers.

You need to divide nums into n pairs such that:

  • Each element belongs to exactly one pair.
  • The elements present in a pair are equal.

Return true if nums can be divided into n pairs, otherwise return false.

Formal Constraints: nums.length == 2 * n. 1 ≤ n ≤ 500 (array length up to 1,000). 1 ≤ nums[i] ≤ 500.

Example Walkthroughs:

Example 1: nums = [3,2,3,2,2,2] → Output: true
Explanation: There are 6 elements in nums, so they should be divided into 6 / 2 = 3 pairs. If nums is divided into the pairs (2, 2), (3, 3), and (2, 2), all elements are matched into equal pairs.

Example 2: nums = [1,2,3,4] → Output: false
Explanation: There is no way to divide nums into 4 / 2 = 2 equal pairs.

Theoretical Framework: The Parity Invariant

An array of length $2N$ can be partitioned entirely into equal disjoint pairs if and only if every distinct integer appears an even number of times. If any integer $x$ appears with an odd frequency ($count[x] pmod 2 eq 0$), at least one element $x$ remains orphaned and cannot find an identical partner.

Because the problem constraints guarantee that $1 le nums[i] le 500$, we can exploit bounded value domains rather than generic hash tables, avoiding dynamic heap allocations entirely.

Approach 1: Fixed Stack Array in C++ (Zero Allocations)

By allocating a fixed 501-element integer buffer directly on the CPU execution stack, we count frequencies in a single cache-coherent pass:

#include <vector>

class Solution {
public:
    bool divideArray(const std::vector<int>& nums) {
        // Stack-allocated frequency buffer for values 1 to 500
        int freq[501] = {0};

        for (int num : nums) {
            freq[num]++;
        }

        // Verify that every distinct element has an even frequency
        for (int i = 1; i <= 500; ++i) {
            if (freq[i] % 2 != 0) {
                return false;
            }
        }

        return true;
    }
};

Approach 2: Single-Pass Set Toggle in TypeScript

In high-level languages like TypeScript, we can track unpaired numbers dynamically using a Set as an odd-frequency accumulator. As we iterate through the array, if the number is already in the set, we remove it (a pair is formed); otherwise, we insert it. At the end of the scan, the set must be empty:

/**
 * Determines whether array can be divided into equal pairs using single-pass set toggling.
 * Time Complexity: O(N) single-pass
 * Space Complexity: O(U) where U is the number of unique elements
 */
export function divideArray(nums: number[]): boolean {
  const unpaired = new Set<number>();

  for (let i = 0; i < nums.length; i++) {
    const val = nums[i];
    if (unpaired.has(val)) {
      unpaired.delete(val);
    } else {
      unpaired.add(val);
    }
  }

  return unpaired.size === 0;
}

Complexity Evaluation Matrix

Comparing execution strategies across memory layout and time complexity:

Algorithmic Strategy Time Complexity Auxiliary Space Memory Locality & Overhead
Sorting + Adjacent Pair Check $O(N log N)$ $O(1)$ in-place Mutates input array; comparison sort bounded by $Omega(N log N)$.
Hash Set Toggle (TypeScript) $O(N)$ single-pass $O(U)$ dynamic set Single iteration pass; dynamic V8 hash set node allocations.
Fixed Frequency Array (Optimal C++) $O(N + M)$ linear $O(M)$ stack buffer Zero heap allocations; $M = 501$ elements fit entirely within L1 cache.

Production Engineering Applications

Parity counting and pair matching mechanisms form the core of several high-throughput computing architectures:

  • Double-Entry Ledger Balancing: In financial reconciliation engines, verifying that every debited ledger entry corresponds to a matching credit transaction before persisting batches to the database.
  • Hardware Socket Buffer Allocation: In network drivers, verifying send/receive packet descriptor buffers are allocated in symmetrical complementary pairs.
  • Garbage Collection Reference Pair Matching: In memory management runtimes, tracking acquire and release handles to ensure zero dangling resource leaks.

Frequently Asked Questions

Why is a fixed array faster than std::unordered_map in C++?

Because the maximum value is bounded by 500, a stack array int freq[501] uses 2,004 contiguous bytes of L1 CPU cache. An unordered_map incurs dynamic heap allocations, hash computation overhead, and pointer dereferencing on every element.

Can this problem be solved using bitwise XOR?

Not directly with a single accumulator. While XOR cancels out identical pairs ($x oplus x = 0$), two distinct odd-count numbers could combine to cancel each other out (e.g. $1 oplus 2 oplus 3 = 0$), producing false positives. A bitmask or frequency array is required.

Does the set toggle approach handle duplicates of 4 or 6 elements?

Yes. When an element appears for the 1st time, it is added. On the 2nd appearance, it is deleted. On the 3rd, it is added back; on the 4th, it is deleted. It perfectly models parity ($frequency pmod 2$).

What is the theoretical lower bound for this problem?

Because every element in the array must be inspected at least once to ensure no unpaired elements exist, the lower bound is strictly $Omega(N)$ linear time.

Conclusion & Performance Takeaways

LeetCode 2206 demonstrates how identifying mathematical invariants—specifically element frequency parity—allows engineers to replace expensive $O(N log N)$ sorting algorithms with single-pass $O(N)$ linear time operations with zero heap allocation overhead.

Full-Stack Algorithm & Systems Architecture

Explore high-performance React, NodeJS, and algorithmic design patterns for enterprise web applications on WebDesigner.LA.

Schedule Technical Consultation →