Facebook Pixel

914. X of a Kind in a Deck of Cards

EasyArrayHash TableMathCountingNumber Theory
Leetcode Link

Problem Description

You have a deck of cards where each card has a number written on it. The array deck[i] represents the number on the ith card.

Your task is to determine if you can partition all the cards into groups that meet these two conditions:

  • Each group must contain exactly x cards, where x > 1 (groups must have at least 2 cards)
  • All cards within the same group must have the same number written on them

Return true if such a partition is possible, otherwise return false.

For example, if you have cards [1,2,3,4,4,3,2,1], you could partition them into groups of size 2: [1,1], [2,2], [3,3], [4,4], so the answer would be true.

The solution works by counting how many times each number appears in the deck using a Counter. Then it finds the greatest common divisor (GCD) of all these counts. If the GCD is at least 2, it means we can divide all cards into groups of size equal to the GCD (or any divisor of the GCD that's greater than 1), satisfying both conditions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about what conditions must be met for a valid partition to exist. If we have a number that appears 6 times, we could potentially group these cards into groups of size 2 (three groups), size 3 (two groups), or size 6 (one group). Notice that 2, 3, and 6 are all divisors of 6.

Now consider if we have two different numbers - one appearing 6 times and another appearing 9 times. For both to be divided into groups of the same size x, that size x must divide both 6 and 9. The possible values are the common divisors of 6 and 9, which are 1 and 3. Since groups must have size greater than 1, only x = 3 works.

This pattern extends to any number of different card values. For all cards to be partitioned into groups of size x:

  • x must divide the count of every distinct number in the deck
  • x must be greater than 1

The set of all possible values for x is exactly the set of common divisors of all the counts. The largest such common divisor is the GCD (greatest common divisor) of all counts. If the GCD is at least 2, then we can use the GCD itself (or any of its divisors greater than 1) as our group size.

Therefore, the problem reduces to finding the GCD of all card counts and checking if it's at least 2. This elegant mathematical insight transforms a seemingly complex partitioning problem into a simple GCD calculation.

Learn more about Math patterns.

Solution Approach

The implementation follows a straightforward approach based on our GCD insight:

  1. Count the occurrences: We use Python's Counter from the collections module to count how many times each distinct number appears in the deck. This creates a dictionary-like object where keys are the card numbers and values are their frequencies.

    cnt = Counter(deck)
  2. Extract frequency values: The cnt.values() gives us all the frequency counts. For example, if deck = [1,2,3,4,4,3,2,1], then cnt would be {1: 2, 2: 2, 3: 2, 4: 2}, and cnt.values() would be [2, 2, 2, 2].

  3. Calculate GCD of all frequencies: We use Python's reduce function with the gcd function from the math module to find the greatest common divisor of all frequency values. The reduce function applies gcd cumulatively:

    • First, it calculates gcd(freq1, freq2)
    • Then gcd(result, freq3)
    • And so on until all frequencies are processed
    reduce(gcd, cnt.values())
  4. Check if valid partition exists: Finally, we check if the resulting GCD is at least 2. If it is, we can partition the cards into groups of size equal to the GCD (or any of its divisors greater than 1).

    return reduce(gcd, cnt.values()) >= 2

The time complexity is O(n + m * log(max_count)) where n is the number of cards and m is the number of distinct values, as we count all cards once and then compute GCD for all frequencies. The space complexity is O(m) for storing the frequency counts.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with deck = [1,1,1,2,2,2,2,2,2].

Step 1: Count the occurrences We count how many times each card number appears:

  • Number 1 appears 3 times
  • Number 2 appears 6 times

So our frequency counts are {1: 3, 2: 6}.

Step 2: Extract frequency values From our counter, we get the list of frequencies: [3, 6].

Step 3: Calculate GCD of all frequencies We need to find the GCD of 3 and 6:

  • Factors of 3: 1, 3
  • Factors of 6: 1, 2, 3, 6
  • Common factors: 1, 3
  • Greatest common divisor: 3

Step 4: Check if valid partition exists Since GCD = 3 ≥ 2, we return true.

Verification: We can indeed partition the cards into groups of size 3:

  • Group 1: [1, 1, 1] (all three 1's)
  • Group 2: [2, 2, 2] (first three 2's)
  • Group 3: [2, 2, 2] (remaining three 2's)

Each group has exactly 3 cards, and all cards in each group have the same number. The partition is valid!

Note that we could also use any divisor of 3 that's greater than 1. Since 3 is prime, there are no other such divisors in this case, but if our GCD had been 6, we could have used group sizes of 2, 3, or 6.

Solution Implementation

1from collections import Counter
2from math import gcd
3from functools import reduce
4from typing import List
5
6class Solution:
7    def hasGroupsSizeX(self, deck: List[int]) -> bool:
8        # Count the frequency of each card value in the deck
9        card_counts = Counter(deck)
10      
11        # Find the GCD of all frequency counts
12        # If the GCD is at least 2, we can partition the deck into groups of equal size
13        # where each group contains cards of the same value
14        common_divisor = reduce(gcd, card_counts.values())
15      
16        # Return True if we can form groups of size >= 2
17        return common_divisor >= 2
18
1class Solution {
2    /**
3     * Determines if the deck can be partitioned into groups where:
4     * 1. Each group has the same size X (X >= 2)
5     * 2. Each group contains cards with the same value
6     * 
7     * The solution works by finding the GCD of all card frequencies.
8     * If the GCD is >= 2, we can partition the deck into groups of that size.
9     * 
10     * @param deck array of integers representing card values
11     * @return true if valid partitioning exists, false otherwise
12     */
13    public boolean hasGroupsSizeX(int[] deck) {
14        // Count the frequency of each card value
15        Map<Integer, Integer> frequencyMap = new HashMap<>();
16        for (int cardValue : deck) {
17            frequencyMap.merge(cardValue, 1, Integer::sum);
18        }
19      
20        // Initialize GCD with the frequency of the first card value
21        int gcdResult = frequencyMap.get(deck[0]);
22      
23        // Calculate GCD of all frequencies
24        for (int frequency : frequencyMap.values()) {
25            gcdResult = gcd(gcdResult, frequency);
26        }
27      
28        // Groups must have size at least 2
29        return gcdResult >= 2;
30    }
31  
32    /**
33     * Calculates the Greatest Common Divisor (GCD) using Euclidean algorithm
34     * 
35     * @param a first number
36     * @param b second number
37     * @return GCD of a and b
38     */
39    private int gcd(int a, int b) {
40        return b == 0 ? a : gcd(b, a % b);
41    }
42}
43
1class Solution {
2public:
3    bool hasGroupsSizeX(vector<int>& deck) {
4        // Count the frequency of each card value in the deck
5        unordered_map<int, int> frequencyMap;
6        for (int cardValue : deck) {
7            ++frequencyMap[cardValue];
8        }
9      
10        // Initialize GCD with the frequency of the first card value
11        // Note: Using deck[0] to get a card value that definitely exists
12        int commonGCD = frequencyMap[deck[0]];
13      
14        // Find the GCD of all frequencies
15        // If all groups can have the same size X, then X must divide all frequencies
16        // The maximum possible X is the GCD of all frequencies
17        for (auto& [cardValue, frequency] : frequencyMap) {
18            commonGCD = gcd(commonGCD, frequency);
19        }
20      
21        // Groups must have at least 2 cards each
22        // Return true if the GCD is at least 2
23        return commonGCD >= 2;
24    }
25};
26
1/**
2 * Determines if a deck of cards can be divided into groups where:
3 * - Each group has the same size X (X >= 2)
4 * - Each group contains cards of the same value
5 * @param deck - Array of integers representing card values
6 * @returns true if deck can be divided into valid groups, false otherwise
7 */
8function hasGroupsSizeX(deck: number[]): boolean {
9    // Count frequency of each card value in the deck
10    const cardFrequencyMap: Record<number, number> = {};
11  
12    for (const cardValue of deck) {
13        cardFrequencyMap[cardValue] = (cardFrequencyMap[cardValue] || 0) + 1;
14    }
15  
16    /**
17     * Calculates the greatest common divisor using Euclidean algorithm
18     * @param a - First number
19     * @param b - Second number
20     * @returns The GCD of a and b
21     */
22    const calculateGCD = (a: number, b: number): number => {
23        return b === 0 ? a : calculateGCD(b, a % b);
24    };
25  
26    // Initialize GCD with the frequency of the first card value
27    let commonGroupSize = cardFrequencyMap[deck[0]];
28  
29    // Find GCD of all card frequencies
30    // This gives us the largest possible group size that divides all frequencies
31    for (const [_, frequency] of Object.entries(cardFrequencyMap)) {
32        commonGroupSize = calculateGCD(commonGroupSize, frequency);
33    }
34  
35    // Groups must have at least 2 cards each
36    return commonGroupSize >= 2;
37}
38

Time and Space Complexity

Time Complexity: O(n + k × log M) where n is the length of the array deck, k is the number of unique values in deck, and M is the maximum count value in the Counter.

  • Creating the Counter takes O(n) time to iterate through all elements in deck
  • The reduce(gcd, cnt.values()) operation performs k-1 GCD computations where k is the number of unique values
  • Each GCD computation between two numbers takes O(log M) time using the Euclidean algorithm, where M is the maximum of the two numbers being compared (in this case, the maximum count value)
  • Total: O(n + k × log M), which simplifies to O(n × log M) in the worst case when k approaches n

Space Complexity: O(k) where k is the number of unique values in deck

  • The Counter object stores at most k key-value pairs where k ≤ n
  • The reduce function uses O(1) additional space for the accumulator
  • The recursive call stack for GCD computations uses O(log M) space
  • Total: O(k + log M), which can be written as O(n + log M) in the worst case when all elements are unique

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Group Size Requirement

Pitfall: A common mistake is thinking that we need to find a specific group size x that works for all cards, rather than understanding that the GCD gives us the maximum possible group size, and any divisor of the GCD greater than 1 also works.

Example: If card frequencies are [6, 9, 12], the GCD is 3. Students might think we can only form groups of size 3, but actually groups of size 3 work because:

  • 6 cards can form 2 groups of 3
  • 9 cards can form 3 groups of 3
  • 12 cards can form 4 groups of 3

Solution: Remember that if GCD = g and g ≥ 2, then any divisor d of g where d ≥ 2 is a valid group size. The algorithm correctly handles this by checking if GCD ≥ 2.

2. Edge Case: Single Card Type

Pitfall: Not handling the case where all cards have the same number correctly, especially when there's only one unique card value in the deck.

Example: deck = [1, 1, 1, 1] should return true (can form groups of 2 or 4).

Solution: The current implementation handles this correctly since reduce(gcd, [4]) returns 4, which is ≥ 2. However, be aware that reduce with a single-element iterable returns that element itself.

3. Empty Deck or Single Card

Pitfall: The code will fail on edge cases like an empty deck or a deck with just one card.

Example:

  • deck = [] will cause reduce to fail with "reduce() of empty sequence with no initial value"
  • deck = [1] should return false but the frequency count would be 1, and reduce(gcd, [1]) returns 1, which correctly gives false

Solution: Add validation at the beginning:

def hasGroupsSizeX(self, deck: List[int]) -> bool:
    if len(deck) < 2:
        return False
  
    card_counts = Counter(deck)
    common_divisor = reduce(gcd, card_counts.values())
    return common_divisor >= 2

4. Misunderstanding GCD Behavior with Multiple Values

Pitfall: Not understanding how reduce(gcd, values) works when there are multiple frequency counts.

Example: For frequencies [4, 6, 8]:

  • First: gcd(4, 6) = 2
  • Then: gcd(2, 8) = 2
  • Result: 2

Some might incorrectly calculate gcd(gcd(4, 6), 8) = gcd(2, 8) = 2 differently or expect a different order of operations.

Solution: The reduce function applies operations left-to-right sequentially, and GCD is associative, so the order doesn't matter. The result will always be the same regardless of computation order.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More