DSAPrep
MediumGreedy

Hand of Straights

Alice has some number of cards and she wants to rearrange the cards into groups so that each group is of size groupSize, and consists of groupSize consecutive cards.

Given an integer array hand where hand[i] is the value written on the ith card and an integer groupSize, return true if she can rearrange the cards, or false otherwise.

Example 1

Input: hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
Output: true
Explanation: Alice's hand can be rearranged as [1,2,3],[2,3,4],[6,7,8].

Example 2

Input: hand = [1,2,3,4,5], groupSize = 4
Output: false
Explanation: Alice's hand cannot be rearranged into groups of 4.

Constraints

  • 1 <= hand.length <= 10^4
  • 0 <= hand[i] <= 10^9
  • 1 <= groupSize <= hand.length
View original on LeetCode β†—

This problem is the same as 1296. Divide Array in Sets of K Consecutive Numbers.

Brute Force: Repeated Extraction

Time O(nΒ²)Space O(n)

Count how many of each card value there are. Repeatedly find the smallest value that still has copies left, and try to peel off one full run of groupSize consecutive values starting there. If any required value is missing, fail immediately.

from collections import Counter
class Solution:
def isNStraightHand(self, hand: list[int], groupSize: int) -> bool:
if len(hand) % groupSize != 0:
return False
count = Counter(hand)
remaining = len(hand)
while remaining > 0:
start = min(v for v in count if count[v] > 0)
for v in range(start, start + groupSize):
if count[v] <= 0:
return False
count[v] -= 1
remaining -= groupSize
return True

Finding the current minimum by scanning the counter is O(n), and it repeats roughly n / groupSize times, so this is O(nΒ²) in the worst case.

Greedy: Sorted Keys, Consume Whole Runs

OptimalTime O(n log n)Space O(n)

The key realization: whatever the smallest remaining card value is, it must be the start of some group β€” no card smaller than it exists to precede it in a run of consecutive values. So instead of repeatedly re-scanning for the minimum, sort the distinct values once. Walk them in order; whenever a value still has need = count[value] copies left, that many groups must all start at value right now β€” consume need copies from each of value, value+1, ..., value+groupSize-1 all at once (failing if any of them doesn’t have enough).

from collections import Counter
class Solution:
def isNStraightHand(self, hand: list[int], groupSize: int) -> bool:
if len(hand) % groupSize != 0:
return False
count = Counter(hand)
for start in sorted(count):
need = count[start]
if need <= 0:
continue
for v in range(start, start + groupSize):
if count[v] < need:
return False
count[v] -= need
return True

Play the walk over the sorted-key ledger below β€” the trace is generated by replaying the code itself, so the chips can never disagree with it. Watch why the smallest remaining card is always forced to start a group, and how leftover copies carry into the next group:

hand[1, 2, 3, 6, 2, 3, 4, 7, 8]groupSize3

Sorted distinct values β€” remaining copies

Β·11
Β·22
Β·32
Β·41
Β·61
Β·71
Β·81
groups locked innone yet
1 / 12
group start (smallest remaining)consumed into the groupneed check (count 0)missing card / strandedused up

Sort the distinct values once and count copies: 1 x1, 2 x2, 3 x2, 4 x1, 6 x1, 7 x1, 8 x1, groupSize = 3. The rule to watch: the smallest remaining card has nothing smaller to precede it in a run, so it has no choice but to start a group.

All nine cards land in three groups of three consecutive values β€” [1,2,3], [2,3,4], [6,7,8]. The same walk fails the instant a window crosses a value that doesn’t exist. Here it is collapsing on a divisibility-valid hand β€” watch the dashed ghost cell in the (4, 5, 6) window:

hand[1, 2, 3, 2, 3, 4, 4, 6, 7]groupSize3

Sorted distinct values β€” remaining copies

Β·11
Β·22
Β·32
Β·42
Β·61
Β·71
groups locked innone yet
1 / 8
group start (smallest remaining)consumed into the groupneed check (count 0)missing card / strandedused up

Second trace: the same sweep rejecting a hand β€” counts are 1 x1, 2 x2, 3 x2, 4 x2, 6 x1, 7 x1, groupSize = 3. The divisibility gate passes (9 % 3 == 0), so this rejection has to come from inside the sweep itself.

Card 5 never existed, so the run (4, 5, 6) can never be completed, and the stranded card 4 ends the hand. (The statement’s second example, [1,2,3,4,5] with groupSize = 4, is rejected even earlier β€” 5 mod 4 is 1, so the divisibility check at the top never lets the sweep run.)

Why it’s correct: processing values in sorted order guarantees that by the time we look at start, every group that could have started before start has already been accounted for β€” so any copies of start still remaining absolutely must begin new groups at start (there is no earlier value left to anchor them to). Committing to that is never a mistake, because delaying it is not even an option: a smaller unused card would have to be the start of its own group, and start is the smallest one left. Complexity: sorting the distinct values is O(n log n); the consumption pass touches each value O(groupSize) times but does O(1) work per group formed, so it does not dominate. Space is O(n) for the counter.