The "Hand of Straights" problem asks you to determine if a list of integers hand
can be rearranged into groups of size groupSize
, where each group consists of groupSize
consecutive numbers.
Each number in hand
must be used exactly once, and no number can appear in more than one group. If it's possible to split the entire hand this way, return True
; otherwise, return False
.
Key Constraints:
groupSize
and contain consecutive numbers.hand
must be used; none can be left out or reused.hand = [1,2,3,6,2,3,4,7,8]
and groupSize = 3
, then the function should return True
because the hand can be rearranged into three groups: [1,2,3], [2,3,4], [6,7,8].
At first glance, one might think of forming all possible groups by brute-force, checking every combination of groupSize
consecutive numbers. However, this quickly becomes infeasible as the size of hand
grows, due to the combinatorial explosion.
Instead, the problem is similar to dealing cards into straight hands in a card game. We need a systematic way to always pick the smallest available card and try to build a straight of length groupSize
from it, repeating until all cards are used. This leads to the insight that we should process the hand in sorted order and track the count of each card.
Using a frequency count (hash map or dictionary), we can efficiently keep track of which numbers are available and how many times. By always starting with the smallest available card, we ensure we don't leave any gaps or impossible-to-fill groups for later.
Let's break down the optimized solution step by step:
hand
isn't divisible by groupSize
, it's impossible to form groups of equal size. Return False
immediately.
hand
.
i
from 0 to groupSize-1
:
card + i
exists in the count map and has at least as many cards as needed.False
(can't form a group).card + i
by the number of groups we're currently forming (which is the count of the starting card).False
, it means we've successfully formed all groups. Return True
.
Design Justification: We use a hash map for O(1) lookups and updates, and process cards in sorted order to avoid leaving "holes" that would make forming consecutive groups impossible later on.
Let's walk through hand = [1,2,3,6,2,3,4,7,8]
with groupSize = 3
:
True
.
Brute-force approach: Would involve generating all possible groupings and checking if any arrangement works. This is exponential in time complexity (O(n!)), which is infeasible for large inputs.
Optimized approach:
hand
. This comes from sorting the unique card values. All other operations (count updates and lookups) are O(N).
The "Hand of Straights" problem is elegantly solved by counting card frequencies and always forming groups starting from the lowest available card. This greedy, frequency-based approach ensures that no cards are left unused or impossible to group later, and is far more efficient than brute-force. The key insight is to use a hash map for fast lookups and to process cards in sorted order, leveraging the properties of consecutive groups. This technique is widely applicable to similar problems involving grouping and ordering constraints.
from collections import Counter
class Solution:
def isNStraightHand(self, hand, groupSize):
if len(hand) % groupSize != 0:
return False
count = Counter(hand)
for key in sorted(count):
occur = count[key]
if occur > 0:
for i in range(groupSize):
if count[key + i] < occur:
return False
count[key + i] -= occur
return True
#include <vector>
#include <map>
using namespace std;
class Solution {
public:
bool isNStraightHand(vector<int>& hand, int groupSize) {
if (hand.size() % groupSize != 0) return false;
map<int, int> count;
for (int card : hand) count[card]++;
for (auto it = count.begin(); it != count.end(); ++it) {
int card = it->first;
int occur = it->second;
if (occur > 0) {
for (int i = 0; i < groupSize; ++i) {
if (count[card + i] < occur) return false;
count[card + i] -= occur;
}
}
}
return true;
}
};
import java.util.*;
class Solution {
public boolean isNStraightHand(int[] hand, int groupSize) {
if (hand.length % groupSize != 0) return false;
TreeMap<Integer, Integer> count = new TreeMap<>();
for (int card : hand) count.put(card, count.getOrDefault(card, 0) + 1);
for (int card : count.keySet()) {
int occur = count.get(card);
if (occur > 0) {
for (int i = 0; i < groupSize; ++i) {
int next = card + i;
if (count.getOrDefault(next, 0) < occur) return false;
count.put(next, count.get(next) - occur);
}
}
}
return true;
}
}
var isNStraightHand = function(hand, groupSize) {
if (hand.length % groupSize !== 0) return false;
let count = new Map();
for (let card of hand) {
count.set(card, (count.get(card) || 0) + 1);
}
let keys = Array.from(count.keys()).sort((a, b) => a - b);
for (let key of keys) {
let occur = count.get(key);
if (occur > 0) {
for (let i = 0; i < groupSize; ++i) {
let next = key + i;
if ((count.get(next) || 0) < occur) return false;
count.set(next, count.get(next) - occur);
}
}
}
return true;
};