150NeetCode 150

126.Hand of Straights

MediumGreedy ยท 5/8

Problem

Alice has a hand of cards, given as an integer array hand where hand[i] is the value on the i-th card. She wants to rearrange all the cards into groups of exactly groupSize cards, where each group consists of groupSize consecutive values.

Return true if she can do this using every card, otherwise false.

Examples

Input:  hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
Output: true
Explanation: [1,2,3], [2,3,4], [6,7,8].
Input:  hand = [1,2,3,4,5], groupSize = 4
Output: false
Explanation: 5 cards cannot be split into groups of 4.
Input:  hand = [1,1,2,2,3,4], groupSize = 3
Output: false
Explanation: Both 1s must start a group, needing two 3s, but there is only one.

Constraints

  • 1 <= hand.length <= 10^4
  • 0 <= hand[i] <= 10^9
  • 1 <= groupSize <= hand.length

๐Ÿ’ก Key Insight

The smallest remaining card has no smaller neighbour to belong to, so it must start a group. That forces the next groupSize - 1 values. Repeating this greedy choice either consumes every card or hits a missing value, in which case the answer is false. The only question is how to find "the smallest remaining card" efficiently โ€” sorting, a TreeMap, or walking back to the start of each run of consecutive values in a HashMap.

Sort and Scan

TimeO(nยฒ) worst caseSpaceO(n)

Intuition

Sort the hand. The first unused card is the smallest remaining, so it starts a group; scan forward for the next unused cards with values +1, +2, ... and mark them used. If any required value is missing, fail.

Algorithm

  1. If hand.length % groupSize != 0, return false.
  2. Sort hand; used[] all false.
  3. For each unused index i: mark it, set need = hand[i] + 1, and scan j > i for unused cards equal to need, marking them, until the group is complete. If the scan passes a value greater than need or runs out, return false.
  4. Return true.

Visualization

sorted: [1,2,2,3,3,4,6,7,8], groupSize = 3
group from 1: take 1, 2 (idx1), 3 (idx3)       remaining: 2,3,4,6,7,8
group from 2: take 2 (idx2), 3 (idx4), 4       remaining: 6,7,8
group from 6: take 6, 7, 8                     all used -> true

Code

java
import java.util.Arrays;

class Solution {
    public boolean isNStraightHand(int[] hand, int groupSize) {
        int n = hand.length;
        if (n % groupSize != 0) {
            return false;
        }
        Arrays.sort(hand);
        boolean[] used = new boolean[n];
        for (int i = 0; i < n; i++) {
            if (used[i]) {
                continue;
            }
            used[i] = true; // smallest remaining card starts a group
            int need = hand[i] + 1;
            int taken = 1;
            for (int j = i + 1; j < n && taken < groupSize; j++) {
                if (used[j] || hand[j] < need) {
                    continue; // used, or a duplicate of an earlier value
                }
                if (hand[j] > need) {
                    return false; // the required value is missing
                }
                used[j] = true;
                need++;
                taken++;
            }
            if (taken < groupSize) {
                return false;
            }
        }
        return true;
    }
}

Complexity

  • Time: O(nยฒ) worst case โ€” each group scan may pass over many used or duplicate cards (plus O(n log n) sort).
  • Space: O(n) โ€” the used array.

Complexity Comparison

Approach Time Space
Sort and Scan O(nยฒ) O(n)
TreeMap of Counts O(n log n) O(n)
HashMap with Run Starts O(n) amortised O(n)

Common Pitfalls

  • Skipping the hand.length % groupSize check โ€” it is a free early exit.
  • Starting a group from an arbitrary card instead of the smallest one in its run; e.g. starting at 2 in [1,2,3,2,3,4] strands the 1.
  • Forgetting duplicates: when the smallest value appears k times, k groups must start there.
  • Removing keys from a TreeMap while iterating over its keySet() โ€” this throws ConcurrentModificationException; re-read firstKey() each round instead.