Given a hand of cards, decide whether they can be arranged into groups of groupSize consecutive integers. The key insight: always start a new group from the smallest unused card — any other choice leads to a dead end.
You hold a hand of integers. Decide whether you can rearrange all of them into groups where each group has exactly groupSize consecutive cards (e.g. 3, 4, 5 or 7, 8, 9). Every card must be used exactly once.
Example: hand = [1,2,3,6,2,3,4,7,8,2,3,5], groupSize = 3. The twelve cards split into four groups: [1,2,3], [2,3,4], [2,3,5], [6,7,8]. Return true. If groupSize = 4 for the same hand (12 cards), we would need three groups of 4; with groupSize = 5we'd need two groups of 5. Whenever hand.length % groupSize !== 0, immediately return false.
groupSize starting there, consuming one copy each of the next groupSize − 1 consecutive cards. If any of those cards is unavailable, the whole arrangement is impossible.hand.length % groupSize !== 0, no valid split exists. Return false immediately.Map<number, number> of card → frequency. Every distinct value becomes a key.start (skip if its count is already 0): let freq = count[start]. We must open freq separate runs beginning here, each consuming one card from start through start + groupSize − 1. If any card in that range has fewer than freq copies, return false; otherwise subtract freq from each.true.count[start] = freq, all freq groups starting at start must be processed at once — subtract freq from each card in the range in a single inner pass, not one at a time.Both approaches are O(n log n) due to sorting, but the count-map version avoids physically rearranging all n cards — it sorts only the distinct values (k ≤ n). The inner loop over groupSize slots runs once per unique starting value and in total touches each card at most once, keeping the work at O(n) after sorting.
[1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8]. Group size: 3. Build a frequency count; then greedily peel runs from the smallest card.1function isNStraightHand(hand: number[], groupSize: number): boolean {2 if (hand.length % groupSize !== 0) return false;34▶ const count = new Map<number, number>();5▶ for (const card of hand) {6▶ count.set(card, (count.get(card) ?? 0) + 1);7▶ }89 const keys = [...count.keys()].sort((a, b) => a - b);1011 for (const start of keys) {12 const freq = count.get(start) ?? 0;13 if (freq === 0) continue; // already fully consumed1415 // We need to open freq groups starting at 'start'16 for (let i = 0; i < groupSize; i++) {17 const card = start + i;18 const available = count.get(card) ?? 0;19 if (available < freq) return false; // can't complete all freq groups20 count.set(card, available - freq);21 }22 }2324 return true;25}
function isNStraightHand(hand: number[], groupSize: number): boolean {
if (hand.length % groupSize !== 0) return false;
const count = new Map<number, number>();
for (const card of hand) {
count.set(card, (count.get(card) ?? 0) + 1);
}
const keys = [...count.keys()].sort((a, b) => a - b);
for (const start of keys) {
const freq = count.get(start) ?? 0;
if (freq === 0) continue; // already fully consumed
// We need to open freq groups starting at 'start'
for (let i = 0; i < groupSize; i++) {
const card = start + i;
const available = count.get(card) ?? 0;
if (available < freq) return false; // can't complete all freq groups
count.set(card, available - freq);
}
}
return true;
}groupSize, no valid partition exists. This O(1) check avoids any further work.Map keeps the keys as numbers (not strings), which matters when sorting later.(a, b) => a - b.freq === 0). Otherwise we know freq separate groups must start here, each needing one copy of start through start + groupSize - 1. We check availability and subtract in one inner pass — this is the key efficiency trick.false, every card has been assigned to a valid consecutive group.true as long as hand.length % 1 === 0, which is trivially true.freq subtraction.[start, start+1, ..., start+groupSize-1] into a result array freq times (or store the groups as a 2-D array built alongside the subtraction loop).function isNStraightHand(hand: number[], groupSize: number): boolean {
if (hand.length % groupSize !== 0) return false;
const count = new Map<number, number>();
for (const card of hand) {
count.set(card, (count.get(card) ?? 0) + 1);
}
const keys = [...count.keys()].sort((a, b) => a - b);
for (const start of keys) {
const freq = count.get(start) ?? 0;
if (freq === 0) continue; // already fully consumed
// We need to open freq groups starting at 'start'
for (let i = 0; i < groupSize; i++) {
const card = start + i;
const available = count.get(card) ?? 0;
if (available < freq) return false; // can't complete all freq groups
count.set(card, available - freq);
}
}
return true;
}Keep the hand sorted. Repeatedly take the current smallest card as the start of a run, then linearly search-and-remove the next groupSize - 1consecutive values. If any is missing, it's impossible.
function isNStraightHand(hand: number[], groupSize: number): boolean {
if (hand.length % groupSize !== 0) return false;
const cards = [...hand].sort((a, b) => a - b);
while (cards.length > 0) {
const start = cards.shift() as number; // smallest remaining
for (let i = 1; i < groupSize; i++) {
const next = start + i;
const idx = cards.indexOf(next); // linear search
if (idx === -1) return false;
cards.splice(idx, 1); // remove one copy
}
}
return true;
}groupSize linear searches and splices over an O(n) array, giving O(n²) work plus the initial sort. Counting card frequencies in a map and consuming runs by arithmetic lookup removes the inner scan.| "consecutive groups of size k" | sorted frequency map + greedy peel from min |
| arrange cards / tasks into fixed-length chains | count + smallest-first greedy |
| hand.length % groupSize !== 0 | instant false — divisibility guard first |
| "Divide array in sets of k consecutive" | same pattern as Hand of Straights |
function isNStraightHand(hand: number[], groupSize: number): boolean {
if (hand.length % groupSize !== 0) return false;
const count = new Map<number, number>();
for (const card of hand) count.set(card, (count.get(card) ?? 0) + 1);
const keys = [...count.keys()].sort((a, b) => a - b);
for (const start of keys) {
const freq = count.get(start) ?? 0;
if (freq === 0) continue;
for (let i = 0; i < groupSize; i++) {
const card = start + i;
const available = count.get(card) ?? 0;
if (available < freq) return false;
count.set(card, available - freq);
}
}
return true;
}isNStraightHand?hand = [1,2,3,6,2,3,4,7,8,2,3,5], groupSize = 3, how many distinct groups are formed?hand = [1,2,3,4], groupSize = 3. What does the algorithm return?available < freq for a card mid-run. What does this failing mean?