Split Array into Consecutive Subsequences

Updated on 24 June, 2025
Split Array into Consecutive Subsequences header image

Problem Statement

Given an integer array nums that is sorted in non-decreasing order, the challenge is to determine if this array can be partitioned into one or more subsequences that meet two key criteria: each subsequence must consist of consecutive integers, and each must have a length of at least three elements. The function should return true if such a partitioning is possible, and false otherwise. Essentially, you are asked to check if the sequence can be broken into pieces where each piece forms a series of at least three numbers that increase consecutively.

Examples

Example 1

Input:

nums = [1,2,3,3,4,5]

Output:

true

Explanation:

nums can be split into the following subsequences:
  
[1,2,3,3,4,5] --> 1, 2, 3
[1,2,3,3,4,5] --> 3, 4, 5

Example 2

Input:

nums = [1,2,3,3,4,4,5,5]

Output:

true

Explanation:

nums can be split into the following subsequences:
  
[1,2,3,3,4,4,5,5] --> 1, 2, 3, 4, 5
[1,2,3,3,4,4,5,5] --> 3, 4, 5

Example 3

Input:

nums = [1,2,3,4,4,5]

Output:

false

Explanation:

It is impossible to split nums into consecutive increasing subsequences of length 3 or more.

Constraints

  • 1 <= nums.length <= 104
  • -1000 <= nums[i] <= 1000
  • nums is sorted in non-decreasing order.

Approach and Intuition

The purpose is to find out if we can rearrange nums into subsequences where every subsequence is strictly increasing by 1 and has at least three numbers. Here’s a clear step-wise breakdown of the approach using the given examples as references:

  1. First, understand how an array sorted in non-decreasing order can form subsequences. Each subsequence extracted should follow the criteria of consecutive integers with a length of 3 or more.

  2. Consider maintaining a frequency count of the array's elements. This count will help track the potential starting elements of any new subsequences or continued subsequences.

  3. For the construction of the subsequences:

    • Check if a current number can be a continuation of an earlier subsequence. If possible, this is often preferable to starting a new subsequence.
    • If the current number does not fit into existing subsequences, check the possibility of starting a new subsequence.
  4. Use the given examples to illustrate:

    • In Example 1, the breakdown formed is [1,2,3] and [3,4,5]. Observe how 3 works flexibly either as a part of a subsequences starting with 1 or starting a new one with 3, 4, 5.

    • Example 2 shows how flexible arrangement allows us to use multiple possible consecution patterns to validate true [1,2,3,4,5] and append another [3,4,5].

    • Example 3 highlights a limitation where no feasible pattern of subsequences can meet the requirements thus returning false.

  5. Technical considerations:

    • If a number n can belong to more than one subsequence, prioritize adding it to a shorter one, since satisfying the minimum length of three is crucial.
    • If using a count mechanism (like a hashmap), after using a number to extend a subsequence, decrease its count. Manage counts so as not to 'run out' of necessary numbers to complete or extend subsequences.

By following the logic of managing counts and potential subsequences, and checking feasibility of each number contributing to an ongoing or new subsequence, we can determine if the array can be resolved as described by the problem constraints.

Solutions

  • C++
  • Java
cpp
class Solution {
public:
    bool canDivide(vector<int> &numbers) {
        int total = numbers.size();
        int segmentStart = 0;

        for (int position = 1; position < total; position++) {
            if (numbers[position] - numbers[position - 1] > 1) {
                if (!validateSegment(numbers, segmentStart, position - 1)) {
                    return false;
                }
                segmentStart = position;
            }
        }
        return validateSegment(numbers, segmentStart, total - 1);
    }
    
private:
    bool validateSegment(vector<int> &vals, int first, int last) {
        int countAtLast = 0;
        int oneLenSeqs = 0;
        int twoLenSeqs = 0;
        int totalSeqs = 0;

        for (int idx = first; idx <= last; idx++) {
            if (idx > first && vals[idx] == vals[idx - 1]) {
                countAtLast++;
            } else if (countAtLast < oneLenSeqs + twoLenSeqs) {
                return false;
            } else {
                twoLenSeqs = oneLenSeqs;
                oneLenSeqs = max(0, countAtLast - totalSeqs);
                totalSeqs = countAtLast;
                countAtLast = 1;
            }
        }

        twoLenSeqs = oneLenSeqs;
        oneLenSeqs = max(0, countAtLast - totalSeqs);
        return oneLenSeqs == 0 && twoLenSeqs == 0;
    }
};

In the provided C++ code, the objective is to determine if an array can be divided into several subsequences of at least three consecutive integers each. To achieve this, the array elements are checked to ensure they can form these required subsequences without any breaks larger than 1 in their sequences.

  • A public function canDivide takes an array of integers (numbers) and checks if the entire array can be split into the desired subsequences.
  • Internally, the canDivide method iterates over the array, tracking start points for each new subsequence whenever a non-consecutive number (difference greater than 1 from the previous number) is found. It calls validateSegment to verify each segment of numbers can form a subsequence.
  • Subsegments are validated using the private method validateSegment. This method counts appearances of each number and checks if subsequences can be formed with remaining numbers. The logic ensures enough elements exist to form required subsequences by mapping current counts against the counts required to complete forming these subsequences.

To summarize, the C++ implementation effectively divides and validates segments of the array by ensuring all parts meet the rules for forming consecutive subsequences of a minimum length of three, ensuring no gaps greater than 1 exist between numbers in any validated subsequence. The main challenge addressed is correctly counting sequences and ensuring there are enough remaining numbers to comply with the rules for subsequences.

java
public class Solution {
    public boolean canSplitIntoSegments(int[] sequence) {
        int length = sequence.length, segmentStart = 0;

        for (int i = 1; i < length; i++) {
            if (sequence[i] - sequence[i - 1] > 1) {
                if (!isValidSegment(sequence, segmentStart, i - 1)) {
                    return false;
                }
                segmentStart = i;
            }
        }
        return isValidSegment(sequence, segmentStart, length - 1);
    }

    private boolean isValidSegment(int[] sequence, int start, int end) {
        int count = 0;
        int subSeq1 = 0;
        int subSeq2 = 0;
        int totalSubsequences = 0;

        for (int i = start; i <= end; i++) {
            if (i > start && sequence[i] == sequence[i - 1]) {
                count++;
            } else {
                if (count < subSeq1 + subSeq2) {
                    return false;
                } else {
                    subSeq2 = subSeq1;
                    subSeq1 = Math.max(0, count - totalSubsequences);
                    totalSubsequences = count;
                    count = 1;
                }
            }
        }
        subSeq2 = subSeq1;
        subSeq1 = Math.max(0, count - totalSubsequences);
        return subSeq1 == 0 && subSeq2 == 0;
    }
}

The provided Java solution addresses the task of splitting an array into consecutive subsequences. The main method, canSplitIntoSegments(int[] sequence), iterates through the array to identify possible subsequences based on consecutive integers. When a gap larger than one is found, the program checks if the current subsequence is valid by calling the isValidSegment(int[] sequence, int start, int end) method.

The isValidSegment method verifies that a segment can be split into consecutive subsequences without any gaps. It maintains counters to track different subsequence parameters like count, which tallies consecutive numbers, and subSeq1 and subSeq2, which store information about subsequences. These are essential in determining if there are enough numbers to complete a subsequence without breaking the consecutive requirement.

  • Key tasks performed:

    • Check each potential subsequence for validity using the gap of more than one between elements as a delimiter.
    • Validate each potential subsequence with isValidSegment to ensure all elements form consecutive subsequences entirely.
  • Technical insights:

    • A conversion from iterating through an outer loop to checking each subsegment individually ensures that all potential subsequences are validated.
    • Leverages element comparison and dynamic subsequence counting within isValidSegment to appropriately validate segments before accepting them.

This solution effectively identifies and verifies subsequences in an array, returning a boolean to indicate if the overall array can be split into valid consecutive subsequences or not.

Comments

No comments yet.