Divide Array in Sets of K Consecutive Numbers: Greedy Grouping
Given an array of integers nums and a positive integer k, determine whether you can divide the array into sets of k consecutive numbers. Each number must be used exactly once.
This is LeetCode 1296: Divide Array in Sets of K Consecutive Numbers, a medium problem that is functionally identical to Hand of Straights (LeetCode 846). If you can solve one, you can solve both.
Why this problem matters
This problem is a clean demonstration of greedy algorithms driven by frequency counting. The pattern of counting occurrences, iterating in sorted order, and greedily consuming elements appears across many interview problems. It also teaches you how to recognize when a greedy choice is provably optimal, which is a skill that transfers to scheduling, partitioning, and resource allocation problems.
The key insight
Think about the smallest number in the array. It must belong to some group, and it can only be the starting element of that group (nothing smaller exists to pair it with). So the group that begins at the smallest number is forced. Once you form that group and remove those numbers, the same logic applies to the next smallest remaining number.
This greedy choice is always correct: at every step, the smallest available number must start a new group. If the required consecutive numbers do not exist, the answer is False.
The solution
from collections import Counter
def isPossibleDivide(nums: list[int], k: int) -> bool:
if len(nums) % k != 0:
return False
count = Counter(nums)
for num in sorted(count):
while count[num] > 0:
for i in range(num, num + k):
if count[i] <= 0:
return False
count[i] -= 1
return True
Here is how each piece works:
- Divisibility check: if the total count of numbers is not divisible by
k, you cannot form complete groups. ReturnFalseimmediately. - Frequency map: use a
Counterto record how many times each number appears. - Sorted iteration: process each unique number from smallest to largest. For each number that still has remaining copies, try to form a group of
kconsecutive values starting from that number. - Group formation: for each value in the range
[num, num + k), check that it exists in the map and decrement its count. If any value has a count of 0 or less when you need it, returnFalse. - Success: if all groups form without issues, return
True.
The greedy insight is simple: the smallest remaining number has no choice. It must start a new group. This forced choice at every step is what makes greedy correct here.
Visual walkthrough
Step 1: Sort and count frequencies
Build a frequency map from nums. Sorted unique values: 1, 2, 3, 4, 5. Note that 3 appears twice.
Step 2: Start from smallest (1). Form group [1, 2, 3].
Decrement counts for 1, 2, 3. Cards 1 and 2 are exhausted. Card 3 still has one copy left.
Step 3: Next smallest available (3). Form group [3, 4, 5].
Decrement counts for 3, 4, 5. All values exhausted. Group [3,4,5] formed.
Step 4: All numbers used. Return True.
Every count is 0. We successfully divided all 6 numbers into 2 groups of 3 consecutive values.
Each step shows the frequency map and highlights which values are being consumed. The greedy approach always picks the smallest available number and tries to extend it into a full consecutive group of size k.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Greedy + frequency map | O(n log n) | O(n) |
The dominant cost is sorting the unique keys. Once sorted, each number is decremented exactly once across all groups, so the inner work is O(n) total. The frequency map takes O(n) space in the worst case where all values are unique.
The building blocks
1. Greedy with frequency counting
Count occurrences, then make locally optimal choices based on what remains. The same pattern appears in problems like Task Scheduler (schedule the most frequent task first) and Top K Frequent Elements (count, then select). The skeleton is always the same: build a counter, iterate in a strategic order, and consume greedily.
count = Counter(items)
for key in sorted(count):
while count[key] > 0:
# form a group starting at key
pass
2. Sorted iteration over unique keys
Processing elements in sorted order enforces a natural grouping constraint. When you iterate from smallest to largest, you guarantee that each element is considered as a group starter before it could be consumed as a continuation of a later group. Sorting unique values (not the full array) keeps things efficient when duplicates are common.
for key in sorted(count):
# process key as potential group start
pass
Edge cases
- Array not divisible by k:
nums = [1, 2, 3],k = 2. Length 3 is not divisible by 2. ReturnFalsebefore doing any work. - Duplicate values:
nums = [1, 1, 2, 2, 3, 3],k = 3. Two copies of 1, 2, 3 form two groups[1,2,3]and[1,2,3]. Thewhile count[num] > 0loop handles this naturally. - Gaps in the sequence:
nums = [1, 2, 4, 5, 6, 7],k = 3. Starting from 1, you need 1, 2, 3 but 3 is missing. ReturnFalse. - k equals 1: every number forms its own group. Always
True(as long as the length is valid). - Single group:
nums = [3, 4, 5],k = 3. One complete group. ReturnTrue.
From understanding to recall
The greedy logic is clean: check divisibility, count, sort, and build consecutive groups from the smallest number up. But in an interview, the details matter. Do you check divisibility first? Do you handle the while count[num] > 0 loop for duplicates? Do you remember to iterate over sorted(count) rather than sorted(nums)?
These small choices are where interviews trip people up. Reading the solution once gives you understanding, but not recall. Spaced repetition drills close that gap. You write the solution from scratch, then again a few days later, then a week later. After a few rounds, the pattern is automatic.
Related posts
- Hand of Straights - Same problem with different framing, grouping cards into consecutive runs
- Group Anagrams - Another grouping problem using hash maps
- Top K Frequent Elements - Frequency counting as a foundation for further processing
Divide Array in Sets of K Consecutive Numbers is a great example of how frequency counting and sorted greedy iteration turn a seemingly complex partitioning problem into something simple. CodeBricks breaks this down into its core building blocks and drills them independently with spaced repetition, so the full solution writes itself when you need it.