LeetCode ยท #846

Hand of Straights Solution

Given hand of cards, determine if you can rearrange them into groups of groupSize consecutive cards.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

Problem #846

๐Ÿ—๏ธ

Pattern

Greedy โ€” sorted grouping

Given hand of cards and group size groupSize, return true if you can rearrange them into groups where each group is groupSize consecutive cards.

Example
Input: hand = [1,2,3,6,2,3,4,7,8], groupSize = 3 Output: true // Groups: [1,2,3], [2,3,4], [6,7,8]
Constraints
โ€ข 1 โ‰ค hand.length โ‰ค 10โด โ€ข 0 โ‰ค hand[i] โ‰ค 10โน โ€ข 1 โ‰ค groupSize โ‰ค hand.length
02
Section Two ยท Approach 1

Sort + Brute Scanning โ€” O(n ยท groupSize)

Sort the array. Repeatedly find the smallest remaining card and try to form a group of consecutive cards starting from it. Mark used cards.

Problem: Marking used cards in an array is O(n) per group. Use a frequency map (TreeMap/Counter) for O(log n) per operation.
Java โ€” Sort + scan
class Solution { public boolean isNStraightHand(int[] hand, int g) { if (hand.length % g != 0) return false; java.util.Arrays.sort(hand); boolean[] used = new boolean[hand.length]; for (int i = 0; i < hand.length; i++) { if (used[i]) continue; int prev = hand[i], cnt = 1; used[i] = true; for (int j = i+1; j < hand.length && cnt < g; j++) { if (!used[j] && hand[j] == prev + 1) { used[j] = true; prev = hand[j]; cnt++; } } if (cnt < g) return false; } return true; } }
Python โ€” Sort + scan
class Solution: def isNStraightHand(self, hand: list[int], g: int) -> bool: if len(hand) % g: return False hand.sort() used = [False] * len(hand) for i in range(len(hand)): if used[i]: continue prev, cnt = hand[i], 1; used[i] = True for j in range(i+1, len(hand)): if not used[j] and hand[j] == prev + 1: used[j] = True; prev = hand[j]; cnt += 1 if cnt == g: break if cnt < g: return False return True
03
Section Three ยท Approach 2

TreeMap/Counter โ€” O(n log n)

Count frequencies. Process smallest card first. For each smallest card, try to form a group of groupSize consecutive values, decrementing counts. If any needed value is missing, return false.

๐Ÿ’ก Mental model: Imagine sorting poker cards on a table by number. Pick the lowest pile. From that number, try to form a run of groupSize consecutive cards by taking one from each pile. If any pile in the run is empty, the hand can't be arranged into straights.
Algorithm
  • If n % groupSize != 0: return false.
  • Build a TreeMap (sorted map) of value โ†’ count.
  • While map is not empty: take smallest key start. For i = start to start + groupSize - 1: if count[i] == 0 โ†’ false. Else count[i]--; if 0, remove from map.
  • Return true.
๐ŸŽฏ When to recognize this pattern: "Can you partition numbers into groups of k consecutive values?" Greedy: always start with the smallest available. Also known as LC 1296 (Divide Array in Sets of K Consecutive Numbers โ€” identical problem).
04
Section Four ยท Trace

Visual Walkthrough

Trace for hand=[1,2,3,6,2,3,4,7,8], groupSize=3.

Hand of Straights โ€” TreeMap Grouping
counts: {1:1, 2:2, 3:2, 4:1, 6:1, 7:1, 8:1} Group 1: start=1 โ†’ need 1,2,3. Dec counts. {2:1, 3:1, 4:1, 6:1, 7:1, 8:1} Group 2: start=2 โ†’ need 2,3,4. Dec counts. {6:1, 7:1, 8:1} Group 3: start=6 โ†’ need 6,7,8. Dec counts. {} โ†’ all grouped! return true โœ“
05
Section Five ยท Implementation

Code โ€” Java & Python

Java โ€” TreeMap
import java.util.*; class Solution { public boolean isNStraightHand(int[] hand, int g) { if (hand.length % g != 0) return false; TreeMap<Integer,Integer> map = new TreeMap<>(); for (int c : hand) map.merge(c, 1, Integer::sum); while (!map.isEmpty()) { int start = map.firstKey(); for (int i = start; i < start + g; i++) { if (!map.containsKey(i)) return false; if (map.get(i) == 1) map.remove(i); else map.put(i, map.get(i) - 1); } } return true; } }
Python โ€” Counter + sorted
from collections import Counter class Solution: def isNStraightHand(self, hand: list[int], g: int) -> bool: if len(hand) % g: return False cnt = Counter(hand) for start in sorted(cnt): while cnt[start] > 0: for i in range(start, start + g): if cnt[i] <= 0: return False cnt[i] -= 1 return True
06
Section Six ยท Analysis

Complexity Analysis

ApproachTimeSpaceTrade-off
Sort + scanO(n ยท groupSize)O(n)Boolean used array
TreeMap/Counter โ† optimalO(n log n)O(n)Sorted map; process smallest first
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseInputWhy It Matters
Not divisible[1,2,3], g=23 % 2 โ‰  0 โ†’ false immediately.
groupSize = 1[5,3,1]Every card is its own group. Always true.
Duplicates[1,1,2,2,3,3], g=3Two groups: [1,2,3] and [1,2,3]. True.
Gap in sequence[1,2,4,5], g=2[1,2] works, but 4,5 aren't consecutive from 3. Wait โ€” [4,5] is valid. True.
Missing consecutive[1,3,5], g=3Need 1,2,3 but 2 missing. False.
โš  Common Mistake: Not always starting from the smallest. If you start from an arbitrary card, you might form a valid group but leave smaller cards stranded. Always greedily start from the smallest available card โ€” it must be the start of a group (no smaller card can precede it).

โ† Back to Greedy problems