Skip to content
← All posts

Hand of Straights: Greedy Grouping with Frequency Counting

6 min read
leetcodeproblemmediumarrayshash-mapgreedy

Hand of Straights (LeetCode 846) asks whether you can rearrange a hand of cards into groups of consecutive values. It is the same problem as LeetCode 1296: Divide Array in Sets of Consecutive Numbers. If you can solve one, you can solve both.

The trick is greedy: always start each group from the smallest card that has not been used yet. If at any point a consecutive card is missing, return False.

The problem

You are given an integer array hand and an integer groupSize. Return True if you can rearrange the cards into groups where each group contains groupSize consecutive cards.

Example: hand = [1, 2, 3, 6, 2, 3, 4, 7, 8], groupSize = 3

hand = [1, 2, 3, 6, 2, 3, 4, 7, 8] — groupSize = 3123623478Sort and group into consecutive sets123Group 1234Group 2678Group 3
Nine cards split into three consecutive groups of size 3. All cards used, so the answer is True.

The output is True because the cards can be divided into [1,2,3], [2,3,4], and [6,7,8].

Why greedy works here

Think about the smallest card in the hand. It has to belong to some group, and it can only be the starting card of that group (there is no smaller card to pair it with as a continuation). So the group starting at the smallest card is forced.

Once you form that group and remove those cards, the same logic applies to the next smallest remaining card. At every step, the smallest available card must start a new group. If you cannot complete the group because a consecutive card is missing, the answer is False.

This greedy choice is always correct because no other assignment of the smallest card would work.

The greedy insight is simple: the smallest available card has no choice. It must be the start of a new group. If the required consecutive cards do not exist, the answer is immediately False.

The brute force alternative

You could try all possible ways to partition the cards into groups. But the number of partitions grows factorially, and even a smart backtracking approach would be far too slow. The greedy method avoids this entirely because the choice at each step is forced.

The greedy solution (sorted + Counter)

from collections import Counter

def isNStraightHand(hand: list[int], groupSize: int) -> bool:
    if len(hand) % groupSize != 0:
        return False

    count = Counter(hand)

    for card in sorted(count):
        while count[card] > 0:
            for i in range(card, card + groupSize):
                if count[i] <= 0:
                    return False
                count[i] -= 1

    return True

Here is how each piece works:

  1. Quick check: if the total number of cards is not divisible by groupSize, it is impossible. Return False immediately.
  2. Count frequencies: use a Counter to track how many of each card value you have.
  3. Iterate in sorted order: for each card value (smallest first), while there are remaining copies, try to form a group starting at that card.
  4. Form the group: check that all groupSize consecutive values exist. Decrement each count. If any count is 0 or negative when you need it, return False.
  5. All groups formed: if you make it through the loop, return True.

Walking through the algorithm

Let's trace through hand = [1, 2, 3, 6, 2, 3, 4, 7, 8] with groupSize = 3.

Step 1: Sort and count frequencies

1x12x23x24x16x17x18x1

Build a frequency map from the hand. Cards sorted: 1, 2, 3, 4, 6, 7, 8.

Step 2: Start from smallest card (1). Form group [1, 2, 3].

1x02x13x14x16x17x18x1

Decrement counts for 1, 2, 3. Card 1 is now exhausted. Group [1,2,3] formed.

Step 3: Next smallest available (2). Form group [2, 3, 4].

1x02x03x04x06x17x18x1

Decrement counts for 2, 3, 4. All three exhausted. Group [2,3,4] formed.

Step 4: Next smallest available (6). Form group [6, 7, 8].

1x02x03x04x06x07x08x0

Decrement counts for 6, 7, 8. All exhausted. Group [6,7,8] formed.

Step 5: All cards used. Return True.

1x02x03x04x06x07x08x0

Every count is 0. We successfully divided all 9 cards into 3 groups of 3 consecutive values.

Each step shows the frequency map and highlights which cards are being consumed. The greedy approach always picks the smallest available card and tries to extend it into a full consecutive group.

Alternative: min-heap approach

Instead of sorting all unique keys upfront, you can use a min-heap to always find the smallest available card in O(log n) time. This also validates that cards are consumed in the right order.

from collections import Counter
import heapq

def isNStraightHand(hand: list[int], groupSize: int) -> bool:
    if len(hand) % groupSize != 0:
        return False

    count = Counter(hand)
    min_heap = list(count.keys())
    heapq.heapify(min_heap)

    while min_heap:
        start = min_heap[0]
        for i in range(start, start + groupSize):
            if count[i] <= 0:
                return False
            count[i] -= 1
            if count[i] == 0:
                if i != min_heap[0]:
                    return False
                heapq.heappop(min_heap)

    return True

The key detail here: when a card count drops to 0, it must be the current heap minimum. If card i is exhausted but it is not the smallest in the heap, that means a smaller card still has remaining copies that cannot form a complete group. That is an immediate False.

Both approaches have the same time complexity, but the heap version catches invalid states earlier.

Complexity analysis

MetricValue
TimeO(n log n) for sorting the unique card values, plus O(n) to process all cards
SpaceO(n) for the frequency map

The dominant cost is sorting. Once sorted, each card is decremented exactly once across all groups, so the inner work is O(n) total.

Building blocks

This problem combines two reusable patterns that appear across many LeetCode problems.

1. Greedy with frequency counting

Count occurrences, then make locally optimal choices based on what is available. This same pattern shows up in Task Scheduler (use the most frequent task first) and Top K Frequent Elements (count, then select).

2. Sorted iteration over unique keys

Process elements in sorted order to enforce a specific grouping or ordering constraint. Sorting the unique values (not the full array) keeps things efficient when there are many duplicates.

The combination of frequency counting and sorted iteration is a powerful toolkit. Any time you need to greedily assign elements to groups, sequences, or slots, think about counting first and processing in order second.

Edge cases

Before submitting, make sure your solution handles these scenarios:

  • Hand not divisible by groupSize: hand = [1, 2, 3], groupSize = 2. The length is 3, which is not divisible by 2. Return False before doing any work.
  • Duplicate cards: hand = [1, 1, 2, 2, 3, 3], groupSize = 3. Two copies of 1, 2, 3 means two groups [1,2,3] and [1,2,3]. The while count[card] > 0 loop handles this naturally.
  • Gaps in the sequence: hand = [1, 2, 4, 5, 6, 7], groupSize = 3. Starting from 1, you need 1, 2, 3 but 3 is missing. Return False.
  • Single group: hand = [5, 6, 7], groupSize = 3. One group that works. Return True.
  • groupSize equals 1: every card forms its own group. Always True (as long as the input is valid).

From understanding to recall

The greedy logic here is clean: count, sort, and greedily build groups from the smallest card up. But in an interview, the details matter. Do you remember to check divisibility first? Do you handle the while count[card] > 0 loop correctly for duplicates? Do you decrement before checking or after?

These small details are where interviews trip people up. Reading the solution once gives you understanding, but it does not give you recall. Spaced repetition drills close that gap. You write the solution from scratch, then do it again a few days later, then a week later. After a few rounds, the pattern is automatic.

Related posts

Hand of Straights is a clean example of how frequency counting plus sorted greedy iteration solves what initially looks like a complex partitioning problem. Learn the building blocks, drill them independently, and the full solution writes itself.