The problem
You hold a hand of cards, each with an integer value, and a number groupSize. Decide whether you can split the whole hand into groups of exactly groupSize cards, where each group is a run of consecutive values (like 3, 4, 5). Every card must be used exactly once.
Input: hand = [1, 2, 3, 6, 2, 3, 4, 7, 8], groupSize = 3
Output: TrueThe groups [1, 2, 3], [2, 3, 4] and [6, 7, 8] use every card.
How to think about it
Start with brute force. Try every way of forming groups with backtracking: pick a card, try to build a run from it, recurse on the rest. The branching is huge and it is exponential in the worst case.
The key insight: the smallest card has no choice. Look at the smallest value x still in the hand. It can’t be the middle or end of a run, because there is nothing smaller to come before it. So it must start a run x, x+1, ..., x+groupSize-1. There is no decision to make, which is exactly when greedy is safe. And if x appears need times, you need need copies of each of the next groupSize - 1 values too.
Process values in sorted order with a counter. Count each value. Walk the distinct values in increasing order. Skip a value whose count has already dropped to 0. Otherwise it is the current smallest, so take its count need and subtract need from each value in its run. If any of those values has fewer than need copies, the hand can’t be split.
Which pattern is this? Greedy with sorting and a hash map. The tell is “split everything into consecutive groups”: the smallest element is forced, so handle it first.
Approach
- If the hand size isn’t a multiple of
groupSize, returnFalsestraight away. - Count how many times each value appears.
- Go through the distinct values in sorted order.
- For a value
xwith a positive countneed, check each value fromxtox + groupSize - 1. If any has a count belowneed, returnFalse. Otherwise subtractneedfrom it. - If every value is handled, return
True.
Solution
from collections import Counter
def is_n_straight_hand(hand, group_size):
if len(hand) % group_size:
return False
cnt = Counter(hand)
for x in sorted(cnt):
if cnt[x] > 0: # x is the smallest card left: it must start runs
need = cnt[x]
for k in range(x, x + group_size):
if cnt[k] < need: # not enough copies to complete every run
return False
cnt[k] -= need
return True
# is_n_straight_hand([1,2,3,6,2,3,4,7,8], 3) -> TrueDry run
The counts start as {1:1, 2:2, 3:2, 4:1, 6:1, 7:1, 8:1}.
x |
cnt[x] |
Action | Counts after |
|---|---|---|---|
| 1 | 1 | start 1 run of 1–3 | {1:0, 2:1, 3:1, 4:1, 6:1, 7:1, 8:1} |
| 2 | 1 | start 1 run of 2–4 | {1:0, 2:0, 3:0, 4:0, 6:1, 7:1, 8:1} |
| 3, 4 | 0 | skip | unchanged |
| 6 | 1 | start 1 run of 6–8 | all zero |
| 7, 8 | 0 | skip | all zero |
No run ran short, so the answer is True.
Complexity
- Time: O(n log n). Sorting the distinct values dominates. Each run start consumes at least
groupSizecards, so there are at mostn / groupSizestarts, each doinggroupSizework: O(n) in total. - Space: O(n). The counter holds up to n distinct values.
Edge cases and pitfalls
- Hand size not divisible by
groupSizefails immediately. groupSize = 1is alwaysTrue: every card is its own run.- Gaps like
[1, 2, 4]with size 3:cnt[3]is 0, so it returnsFalse.Counterreturns 0 for missing keys instead of raising. - Subtracting one at a time with a heap also works, but handling all
needcopies at once is simpler and faster. - Starting runs at an arbitrary card instead of the smallest breaks the “no choice” argument.
Related problems
- Longest Consecutive Sequence (LeetCode 128): also finds where runs start, using a set.
- Assign Cookies (LeetCode 455): sort, then always deal with the smallest item first.
- Merge Triplets to Form Target Triplet (LeetCode 1899): another greedy where each element is either forced or harmless.
- 1.Assign Cookies (LeetCode 455)
- 2.Jump Game (LeetCode 55)
- 3.Jump Game II (LeetCode 45)
- 4.Gas Station (LeetCode 134)
- 5.Partition Labels (LeetCode 763)
- 6.Task Scheduler (LeetCode 621)
- 7.Hand of Straights (LeetCode 846)
- 8.Remove Covered Intervals (LeetCode 1288)
- 9.Valid Parenthesis String (LeetCode 678)
- 10.Merge Triplets to Form Target Triplet (LeetCode 1899)
- 11.Candy (LeetCode 135)