Problem
Given a hand of cards and a group size W, determine whether the hand can be
split entirely into groups of W consecutive-value cards.
Signal
"Split into consecutive-run groups" over a multiset of values is greedy by necessity: whatever the smallest remaining card is, it must be the start of some group — nothing smaller exists to pair it with a run beginning earlier.
Approach
Count each card's frequency. Repeatedly look at the smallest value that still
has a positive count, and consume one card each of that value through
value + W - 1 to form a group. If any of those W required values is
missing (count is zero), the hand can't be split. Keep doing this until every
count reaches zero.
Skeleton
counts = Counter(hand)
for start in sorted(counts):
need = counts[start]
if need <= 0: continue
for v in range(start, start + W):
if counts[v] < need: return False
counts[v] -= need
return True
Solution
from collections import Counter
def is_n_straight_hand(hand: list[int], group_size: int) -> bool:
if len(hand) % group_size != 0:
return False
counts = Counter(hand)
for start in sorted(counts):
need = counts[start]
if need <= 0:
continue
for v in range(start, start + group_size):
if counts[v] < need:
return False
counts[v] -= need
return True
Complexity
O(n log n) time — dominated by sorting the distinct values; the group-forming sweep itself is O(n · W) in the worst case. O(n) space for the counts.
Pitfalls
- Iterating
sorted(counts)once and using each value's current remaining count asneed(rather than re-scanning for the global minimum each time) is what keeps this from becoming quadratic re-sorting. - Forgetting the early
len(hand) % group_size != 0check wastes work — an uneven total can never split evenly, no matter what the values are. - This greedily commits to using the smallest remaining card as a group start — that's provably safe here (no smaller card can ever need it later), which is exactly the kind of exchange-argument justification worth stating if an interviewer asks "why does greedy work here?"