Problem Overview
LeetCode 846 Hand of Straights gives you an array of integers representing a hand of cards and an integer groupSize. Your goal is to determine whether you can rearrange all the cards into groups where each group has exactly groupSize consecutive cards.
For example, hand = [1,2,3,6,2,3,4,7,8] with groupSize = 3 can be split into [1,2,3], [2,3,4], and [6,7,8] — three consecutive groups of size 3, so the answer is true. If groupSize = 2 and the hand has an odd total of cards, the answer is immediately false.
This problem sits at the intersection of greedy algorithms and frequency counting. Sorting the unique card values and consuming them greedily from the smallest produces an elegant O(n log n) solution that avoids backtracking entirely.
- hand: integer array representing card values
- groupSize: required size of each consecutive group
- Each group must contain exactly groupSize consecutive integers
- Every card must be used exactly once across all groups
- Return true if a valid arrangement exists, false otherwise
Greedy from Smallest
The greedy insight is straightforward: always start a new group from the smallest card currently available. The smallest card cannot be in the middle or end of a group — it has no smaller neighbor to precede it. Therefore it must begin some group, and we should form that group immediately.
Once we commit to starting a group at value v, we need one copy each of v, v+1, v+2, ..., v+groupSize-1. If any of those values is missing or fully consumed, the arrangement is impossible and we return false. If they all exist we decrement their counts and continue to the next smallest available card.
This greedy choice is safe because the smallest card has a unique forced role: it must be a group leader. Deferring that role to a later step cannot help — the card will still need to start a group at some point, and acting on it now never invalidates future decisions.
Greedy Starting Rule
Always start groups from the smallest available card. This greedy choice is optimal because the smallest card MUST start some group — it cannot be in the middle or end of one. Consuming it immediately and checking forward is safe and never misses a valid arrangement.
Algorithm with Counter
Build a frequency counter from the hand array. Then collect the sorted unique values. Iterate through these unique values in ascending order. For each value v whose count is still greater than zero, you must form exactly count[v] groups starting at v — one group for every remaining copy of v.
For each such group, decrement count[v], count[v+1], ..., count[v+groupSize-1] by the same amount (count[v]). If any of those counts would go negative, the hand cannot be arranged validly; return false immediately.
After processing all unique values, if no step triggered an early false return, all cards have been consumed into valid groups and you return true. The time complexity is O(n log n) for sorting plus O(n * groupSize) for the greedy sweep, with O(n) space for the counter.
- 1Count frequencies: build counter map from hand array
- 2Sort unique values in ascending order
- 3For each unique value v in sorted order:
- 4 If count[v] == 0: skip (already consumed)
- 5 needed = count[v] (number of groups to start at v)
- 6 For k from 0 to groupSize-1:
- 7 count[v + k] -= needed
- 8 If count[v + k] < 0: return false
- 9Return true (all groups formed successfully)
Why Greedy Works
The correctness argument relies on the forced role of the minimum element. At every step, the globally smallest card with a positive count has no smaller neighbor available. It cannot participate in any group unless it is the leftmost element of that group. Delaying its consumption would only lead to the same forced choice later with potentially fewer remaining cards to pair it with.
Forming all required groups starting at the current minimum simultaneously — consuming count[v] groups at once — is equivalent to processing them one by one. The order within a batch does not matter because all groups in a batch have identical structure.
If at any point a required successor card v+k has been exhausted (count[v+k] < needed), no rearrangement can supply the missing cards. Cards are fixed; the greedy failure is a true impossibility, not a missed branch.
Problem Family
This is equivalent to the "divide hand of cards" and "split array into consecutive subsequences" problem family (LeetCode 659). The greedy-from-smallest approach solves all variants: whenever the minimum element has a forced role, greedy consumption is both necessary and sufficient.
Walk-Through Example
Consider hand = [1,2,3,6,2,3,4,7,8] with groupSize = 3. First, count frequencies: {1:1, 2:2, 3:2, 4:1, 6:1, 7:1, 8:1}. Total cards = 9, which is divisible by 3, so we proceed.
Sorted unique values: [1, 2, 3, 4, 6, 7, 8]. Process v=1: count[1]=1, form 1 group [1,2,3]. Decrement counts: {1:0, 2:1, 3:1, 4:1, 6:1, 7:1, 8:1}. Process v=2: count[2]=1, form 1 group [2,3,4]. Decrement: {2:0, 3:0, 4:0, 6:1, 7:1, 8:1}.
Process v=3: count[3]=0, skip. Process v=4: count[4]=0, skip. Process v=6: count[6]=1, form 1 group [6,7,8]. Decrement: {6:0, 7:0, 8:0}. All counts reach zero — return true.
- 1hand=[1,2,3,6,2,3,4,7,8], groupSize=3, total=9 (divisible by 3 — proceed)
- 2Counter: {1:1, 2:2, 3:2, 4:1, 6:1, 7:1, 8:1}
- 3v=1, needed=1: form [1,2,3] → counts {1:0,2:1,3:1,4:1,...}
- 4v=2, needed=1: form [2,3,4] → counts {2:0,3:0,4:0,...}
- 5v=3: count=0, skip. v=4: count=0, skip
- 6v=6, needed=1: form [6,7,8] → counts {6:0,7:0,8:0}
- 7All counts zero → return true
Code Walkthrough — Python and Java
In Python, use collections.Counter for frequencies and sorted() on its keys. The loop is a single pass over sorted unique keys: for each key with a positive count, iterate groupSize steps decrementing counts. If any goes negative raise False. Time complexity O(n log n + n*W) where W = groupSize, space O(n).
In Java, use a TreeMap<Integer, Integer> which maintains keys in sorted order, eliminating the need for an explicit sort step. Iterate entrySet(), and for each entry with a positive value, decrement a window of groupSize successive keys. TreeMap.getOrDefault handles missing keys cleanly.
Both implementations benefit from the early-exit check: if hand.length % groupSize != 0, immediately return false before building any data structures. This eliminates an entire class of impossible inputs in O(1).
Early Exit Optimization
Always check hand.length % groupSize == 0 first. If total cards are not divisible by groupSize, it is impossible to form complete groups with no leftover cards — return false immediately without building the counter or sorting.