You are given an integer array hand where hand[i] is the value written on the ith card and an integer groupSize.
You want to rearrange the cards into groups so that each group is of size groupSize, and card values are consecutively increasing by 1.
Return true if it's possible to rearrange the cards in this way, otherwise, return false.
Example 1:
Input: hand = [1,2,4,2,3,5,3,4], groupSize = 4
Output: trueExplanation: The cards can be rearranged as [1,2,3,4] and [2,3,4,5].
Example 2:
Input: hand = [1,2,3,3,4,5,6,7], groupSize = 4
Output: falseExplanation: The closest we can get is [1,2,3,4] and [3,5,6,7], but the cards in the second group are not consecutive.
Constraints:
1 <= hand.length <= 10000 <= hand[i] <= 10001 <= groupSize <= hand.length
You should aim for a solution as good or better than O(nlogn) time and O(n) space, where n is the size of the input array.
It is observed that to form a group, the minimum value should be the starting value of the group. Additionally, the minimum value in the array serves as the starting value for one or more groups based on its frequency. Can you think of an efficient way to determine the frequencies of array elements? Maybe a specific data structure can be useful here.
We can use a hash map to store the elements along with their frequencies. Additionally, we sort the given array. Then, we iterate through the sorted array and try to form groups by decrementing the frequency count. If we fail to form a group at any step, we immediately return false. Can you think why this works?
Sorting ensures we start with the smallest available value, while the hash map helps track element availability using frequency counts. At each step, we pick the smallest available value x and attempt to form a group from x to x + groupSize - 1. If all elements are present based on their frequency counts, we decrement their counts as we iterate. If we successfully form all groups, we return true; otherwise, we return false.
Before attempting this problem, you should be comfortable with:
We are given a hand of cards and a group size. The goal is to check whether we can divide all cards into groups of size groupSize such that:
A simple and intuitive way to approach this is:
x, we must also have x + 1, x + 2, ..., x + groupSize - 1Sorting the hand helps because it ensures we always process cards in increasing order, which naturally enforces the consecutive requirement.
A frequency map count helps us track how many times each card is still available.
groupSize:false immediately (grouping is impossible)0), skip itnum:num to num + groupSize - 1:false1trueWe need to split the cards into groups of size groupSize, where each group is made of consecutive numbers, and every card is used exactly once.
A good strategy is to always start building a group from the smallest card value that is still available.
If we can always extend that smallest value into a full consecutive group, then the hand is valid.
To do this efficiently:
countWhen we start a group from first (the smallest value in the heap), we must use:first, first+1, ..., first+groupSize-1
If at any point a required value is missing, grouping is impossible.
The heap also helps ensure we remove values in the correct order when their count reaches zero.
groupSize, return false.count where count[x] is how many times card x appears.minH.first be the smallest available value (minH[0])firsti from first to first + groupSize - 1:i is not in count, return false (missing a needed card)count[i] by 1 because we use one card of value icount[i] becomes 0:trueclass Solution:
def isNStraightHand(self, hand: List[int], groupSize: int) -> bool:
if len(hand) % groupSize:
return False
count = {}
for n in hand:
count[n] = 1 + count.get(n, 0)
minH = list(count.keys())
heapq.heapify(minH)
while minH:
first = minH[0]
for i in range(first, first + groupSize):
if i not in count:
return False
count[i] -= 1
if count[i] == 0:
if i != minH[0]:
return False
heapq.heappop(minH)
return TrueWe want to check if the cards can be divided into groups of size groupSize, where each group consists of consecutive numbers and every card is used exactly once.
Instead of explicitly forming each group, we can think in terms of how many groups are currently “open” and waiting to be extended.
As we process card values in increasing order:
The key idea is:
open_groups waiting for the current number, we must have at least that many cards of the current valuegroupSize consecutive numbersA queue is used to remember how many new groups were started at each step, so we can close them after groupSize steps.
groupSize, return false.open_groups: number of groups currently waiting to be extendedlast_num: previous card value processedq to track how many groups were started at each valuenum:num is not consecutive to last_num, return falseopen_groups > count[num], return false (not enough cards to extend groups)num:new_groups = count[num] - open_groupsnew_groups into the queuelast_num = numopen_groups = count[num]groupSize:open_groupsgroupSizetrue only if open_groups == 0class Solution:
def isNStraightHand(self, hand, groupSize):
if len(hand) % groupSize != 0:
return False
count = Counter(hand)
q = deque()
last_num, open_groups = -1, 0
for num in sorted(count):
if ((open_groups > 0 and num > last_num + 1) or
open_groups > count[num]
):
return False
q.append(count[num] - open_groups)
last_num = num
open_groups = count[num]
if len(q) == groupSize:
open_groups -= q.popleft()
return open_groups == 0We need to split the hand into groups of size groupSize, where each group is made of consecutive numbers and every card is used exactly once.
This approach uses only a frequency map and a simple rule:
num, we first walk left to find the earliest possible start of its runstart - 1 still exists in the hand)start, we build a group:start, start+1, ..., start+groupSize-1By always forming groups from the earliest start in a run, we avoid skipping needed smaller cards and ensure groups remain consecutive.
len(hand) is not divisible by groupSize, return false.count for all card values.num in the hand:num:start = numcount[start - 1] > 0, decrement startstart up to num:count[start] > 0:starti from start to start + groupSize - 1:count[i] == 0, return false (missing a needed card)count[i]start forward by 1 and continuetrue.class Solution:
def isNStraightHand(self, hand: List[int], groupSize: int) -> bool:
if len(hand) % groupSize != 0:
return False
count = Counter(hand)
for num in hand:
start = num
while count[start - 1]:
start -= 1
while start <= num:
while count[start]:
for i in range(start, start + groupSize):
if not count[i]:
return False
count[i] -= 1
start += 1
return TrueIf the total number of cards is not divisible by groupSize, it is impossible to form complete groups. Skipping this initial check wastes time processing the array and may lead to subtle bugs where the algorithm appears to succeed but produces an invalid grouping.
Groups must be formed starting from the smallest available card value to ensure consecutive sequences work correctly. Starting from an arbitrary card can leave smaller cards stranded without enough consecutive neighbors to form a valid group. Always process cards in sorted order or use a min-heap to find the smallest available value.
When forming a group, each card in the consecutive sequence must have its count decremented. A common bug is forgetting to decrement or decrementing the wrong key, which causes cards to be reused or leaves cards unused. Additionally, when a card's count reaches zero, it must be handled correctly to avoid checking for cards that no longer exist.