AlgoMaster Logo

Split Array into Consecutive Subsequences

mediumFrequency8 min readUpdated June 23, 2026

Understanding the Problem

We need to partition every element in the sorted array into one or more groups, where each group forms a consecutive sequence of at least length 3. Every element must belong to exactly one group (since duplicates appear multiple times, each copy is assigned separately).

The difficulty is deciding where each element goes. When we reach a number like 4, we can use it to extend an existing subsequence that ends at 3, or start a new subsequence beginning at 4. A wrong choice early on can make it impossible to form valid subsequences later. For example, in [1,2,3,3,4,5], if we greedily build one long subsequence 1,2,3,4,5, we strand the second 3 by itself and the answer comes out false, even though splitting into [1,2,3] and [3,4,5] works.

Because the array is sorted, we process elements in increasing order, and when we reach a number, every smaller number has already been assigned. That lets us decide each element locally, without backtracking.

Key Constraints:

  • nums.length <= 10^4 → An O(n^2) solution passes, and O(n) or O(n log n) is comfortable.
  • -1000 <= nums[i] <= 1000 → Values stay within int, so there is no overflow concern, and counting with a hash map is cheap.
  • nums is sorted → We can process elements left to right and make greedy decisions.

Approach 1: Track Every Subsequence in a List

Intuition

Build the subsequences directly. Keep a list of the open subsequences we have started so far, where each entry records the value the subsequence currently ends at and how long it is. Because the array is sorted, when we reach num, any subsequence that could accept it must end at exactly num - 1.

So for each num, we look for an open subsequence ending at num - 1 and append num to it. If several end at num - 1, we extend the shortest one, since the shortest subsequence is the one most at risk of finishing below length 3. If no subsequence ends at num - 1, we start a new one of length 1 ending at num. After all numbers are placed, the split is valid only if every subsequence reached length 3 or more.

Extending the shortest candidate first is what makes this correct. A subsequence that already has length 3 or more is safe no matter what happens next, so spending the new element on a still-short subsequence is never worse, and it protects the subsequence that has the most to lose.

Algorithm

  1. Keep a list subs, where each entry is (endValue, length) for an open subsequence.
  2. For each num in the sorted array:
    • Scan subs for entries with endValue == num - 1 and pick the one with the smallest length.
    • If one is found, set its endValue to num and increment its length.
    • Otherwise, add a new entry (num, 1).
  3. After processing every number, return true if every entry has length >= 3, and false otherwise.

Example Walkthrough

1subs = []. num=1: no subsequence ends at 0. Start new → subs = [(1,1)]
0
1
num
1
2
2
3
3
3
4
4
5
5
1/7

Code

The cost here comes from scanning the whole list of open subsequences for every element. The next approach removes that scan: instead of choosing which subsequence to extend, it tracks how many subsequences are waiting for each value, which lets each element be placed in O(1).

Approach 2: Greedy with Two Hash Maps

Intuition

We can avoid the scan in Approach 1 by not tracking each subsequence individually. For every number, there are two ways to place it:

  1. Extend an existing subsequence that ends at num - 1 by appending num to it.
  2. Start a new subsequence beginning at num, which requires num + 1 and num + 2 to also be available.

Prefer extending over starting new. Extending an existing subsequence consumes only the current number, while starting a new one also reserves num + 1 and num + 2, future numbers that another subsequence might need. Extending therefore keeps more options open and never makes a previously satisfiable input fail.

We maintain two hash maps:

  • freq: how many copies of each number are still unassigned.
  • tails: how many existing subsequences are waiting to be extended by a specific number. If tails[5] = 2, two subsequences currently end at 4 and can take a 5 next.

Algorithm

  1. Build a frequency map from the input array.
  2. Iterate through each number in the sorted array.
  3. If freq[num] == 0, this copy was already used. Skip it.
  4. If tails[num] > 0, append num to an existing subsequence. Decrement tails[num], increment tails[num + 1].
  5. Otherwise, try to start a new subsequence: check if freq[num + 1] > 0 and freq[num + 2] > 0. If so, consume all three and set tails[num + 3]++.
  6. If neither option works, return false.
  7. If we process all elements successfully, return true.

Example Walkthrough

1Initial: freq={1:1, 2:1, 3:2, 4:1, 5:1}, tails={}
0
1
1
2
2
3
3
3
4
4
5
5
1/6

Code

The two-hash-map approach runs in O(n) and is the most practical of the three. It works because it never needs the individual lengths of subsequences: a subsequence becomes safe once it reaches length 3, and starting a new one always reserves three numbers up front, so no subsequence created by this method can ever finish too short. The final approach takes a different route, tracking each subsequence and its length explicitly with a heap, which makes the greedy choice from Approach 1 efficient.

Approach 3: Greedy with Min-Heap

Intuition

This is the list-based idea from Approach 1, made efficient. We still track each subsequence as (end_value, length), but we store the entries in a min-heap ordered first by end_value and then by length. For each num, the smallest entries are the ones ending earliest, so we can find and remove a subsequence ending at num - 1 in logarithmic time instead of scanning a list.

When several subsequences end at num - 1, the heap ordering by length means we extend the shortest first. A shorter subsequence is closer to violating the length-3 requirement, so giving it the current number first protects the subsequence with the least margin while leaving longer, already-safe subsequences alone.

After all numbers are placed, every subsequence remaining in the heap must have length 3 or more.

Algorithm

  1. Create a min-heap that orders entries by (end_value, length).
  2. For each number in the sorted array:
    • Remove all heap entries where end_value < num - 1 (these subsequences can no longer be extended). If any of them have length < 3, return false.
    • If the heap has an entry with end_value == num - 1, pop the one with the smallest length, extend it to (num, length + 1), and push it back.
    • Otherwise, push a new entry (num, 1).
  3. After processing all elements, check that every remaining entry in the heap has length >= 3.

Example Walkthrough

1Heap=[]. num=1: no entry ends at 0. Start new → push (1,1)
0
1
num
1
2
2
3
3
3
4
4
5
5
1/7

Code