Example 1
ready- Input
hand = [ 1, 2, 3, 6, 2, 3, 4, 7, 8 ] groupSize = 3- Expected
true- Why
- the cards form [1,2,3], [2,3,4], and [6,7,8]
A shuffled hand can hide several perfect runs — or one missing card that ruins all of them.
You're given an integer array hand and an integer groupSize. Decide whether every
card can be rearranged into groups of exactly groupSize cards where each group contains
consecutive values, meaning each value is exactly one greater than the previous one.
Return true when such a rearrangement exists and false otherwise. Every copy of a
duplicate card is separate and must be used exactly once.
1 <= hand.length <= 10_0001 <= groupSize <= hand.length0 <= hand[i] <= 1_000_000_000Hints
Every group has the same size. If hand.length isn't divisible by groupSize, no
rearrangement can use every card.
Look at the smallest card that remains. Could it sit anywhere except at the beginning of a consecutive group?
Count each value. If the smallest remaining value has k copies, you must start k
groups there and remove k copies of every next value in those runs.
How would you return the actual groups while keeping O(n log n) time and O(n) extra space?
Visible cases
hand = [
1,
2,
3,
6,
2,
3,
4,
7,
8
]
groupSize = 3truehand = [
1,
2,
3,
4,
5
]
groupSize = 4falsehand = [
1,
2,
3,
4,
5,
6
]
groupSize = 2trueInterview signal
The smallest remaining card leaves you no freedom: it must begin a run.
Sort all cards. Repeatedly take the smallest remaining value, search for every next value needed by its group, and remove each match. A multiset is a collection that keeps duplicate values, so removing one copy never erases the others.
def is_n_straight_hand(hand: list[int], group_size: int) -> bool:
if len(hand) % group_size != 0:
return False
cards = sorted(hand)
while cards:
start = cards[0]
for card in range(start, start + group_size):
try:
index = cards.index(card)
except ValueError:
return False
cards.pop(index)
return TrueTime O(n²) — repeated linear searches and removals dominate. Space O(n) for the sorted copy.
Replace physical removals with a frequency map. Visit distinct values in sorted order.
If the current smallest value has copies cards left, all copies must begin groups, so
subtract that amount from every value in the required run. A shortage makes the hand
impossible immediately.
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):
copies = counts[start]
if copies == 0:
continue
for card in range(start, start + group_size):
if counts[card] < copies:
return False
counts[card] -= copies
return TrueThe exchange argument is a proof that replacing another arrangement with the greedy
choice can't destroy a solution. Let x be the smallest remaining card. In any valid
arrangement, x can't sit after another card in its group: that predecessor would be
smaller than x, but no smaller card remains. So every valid arrangement must start a
group at x. Exchange whichever group contains x for the forced run
x, x + 1, ..., x + groupSize - 1; it uses exactly the cards every solution must reserve
for that start. Repeating the forced exchange makes the smallest-first choice globally optimal.
Missing any required successor proves no arrangement exists.
Time O(n log n) — sorting the distinct values dominates; frequency consumption is linear in the cards removed. Space O(n) for the counts and sorted keys.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
true