2970. Count the Number of Incremovable Subarrays I
Problem Description
You are given a 0-indexed array of positive integers nums
.
A subarray is called incremovable if removing it from nums
results in the remaining array being strictly increasing. For example, in the array [5, 3, 4, 6, 7]
, the subarray [3, 4]
is incremovable because removing it gives us [5, 6, 7]
, which is strictly increasing.
Your task is to count the total number of incremovable subarrays in nums
.
Key points to understand:
- A subarray is a contiguous non-empty sequence of elements
- An empty array is considered strictly increasing
- Strictly increasing means each element is strictly greater than the previous one (no equal values allowed)
The solution uses a two-pointer approach to handle two main scenarios:
-
Removing subarrays from the end: After removal, only a prefix of the original array remains. The code finds the longest strictly increasing prefix using pointer
i
. If the entire array is already strictly increasing (wheni == n-1
), then any subarray can be removed, giving usn * (n + 1) // 2
total subarrays. -
Removing subarrays from the middle: After removal, we keep both a prefix and a suffix of the original array. The code uses pointer
j
to iterate through possible suffix starting positions from right to left. For each valid suffix position, it adjusts pointeri
to find the longest prefix wherenums[i] < nums[j]
, ensuring the combined prefix and suffix form a strictly increasing sequence.
The algorithm counts all valid subarrays whose removal maintains the strictly increasing property by considering both cases: removing from the end (counted as i + 2
subarrays) and removing from the middle while maintaining valid prefix-suffix combinations.
Intuition
The key insight is to think about what remains after removing a subarray, rather than what we remove. When we remove a contiguous subarray, we're left with either:
- Nothing (remove everything)
- Only a prefix of the original array
- Only a suffix of the original array
- Both a prefix and a suffix (with a gap in the middle)
For the result to be strictly increasing, whatever remains must form a strictly increasing sequence.
Let's start with the simplest case: if the entire array is already strictly increasing, then removing ANY subarray will leave us with a strictly increasing result (or empty, which counts as strictly increasing). This gives us n * (n + 1) // 2
total subarrays.
For the general case, we need to identify valid prefixes and suffixes that can be kept. A valid prefix is one that is strictly increasing from the start. Similarly, a valid suffix is one that is strictly increasing to the end.
The crucial observation is that we can categorize all removals into two types:
- Remove everything from some point onwards - keeping only a prefix
- Remove a middle portion - keeping both a prefix and a suffix
For type 1, if we have a strictly increasing prefix of length i+1
(indices 0 to i), we can remove:
- Everything from index
i+1
onwards:[i+1, n-1]
- Everything from index
i
onwards:[i, n-1]
- Everything from index
i-1
onwards:[i-1, n-1]
- And so on, down to removing the entire array
[0, n-1]
This gives us i + 2
possible removals.
For type 2, we need to ensure that when we keep both a prefix and suffix, the last element of the prefix is less than the first element of the suffix (to maintain strict increasing property). We iterate through possible suffix starting points from right to left. For each suffix starting at position j
, we find the longest valid prefix where nums[i] < nums[j]
. Each such valid combination allows i + 2
different removals (we can start removing from any position from 0 to i+1, and end just before j).
The algorithm efficiently counts both types by first finding the longest increasing prefix, then iterating through valid suffixes while adjusting the prefix pointer to maintain the strictly increasing property between prefix and suffix.
Learn more about Two Pointers and Binary Search patterns.
Solution Approach
The implementation uses a two-pointer technique to efficiently count all incremovable subarrays. Here's how the algorithm works step by step:
Step 1: Find the longest strictly increasing prefix
i, n = 0, len(nums)
while i + 1 < n and nums[i] < nums[i + 1]:
i += 1
We use pointer i
to find the last index of the longest strictly increasing prefix. This prefix spans from index 0 to index i
.
Step 2: Handle the special case - entire array is strictly increasing
if i == n - 1: return n * (n + 1) // 2
If i
reaches n-1
, the entire array is already strictly increasing. In this case, any subarray can be removed and the result will still be strictly increasing (or empty). The total number of subarrays in an array of length n
is n * (n + 1) // 2
.
Step 3: Count subarrays that leave only a prefix
ans = i + 2
For the general case, we first count all subarrays whose removal leaves only a prefix (or nothing). These are:
- Remove
nums[i+1,...,n-1]
(keep prefix up to indexi
) - Remove
nums[i,...,n-1]
(keep prefix up to indexi-1
) - Remove
nums[i-1,...,n-1]
(keep prefix up to indexi-2
) - ... and so on
- Remove
nums[0,...,n-1]
(keep nothing)
This gives us i + 2
possible removals.
Step 4: Count subarrays that leave both prefix and suffix
j = n - 1 while j: while i >= 0 and nums[i] >= nums[j]: i -= 1 ans += i + 2 if nums[j - 1] >= nums[j]: break j -= 1
We use pointer j
to iterate through possible suffix starting positions from right to left:
- Start with
j = n - 1
(suffix contains only the last element) - For each suffix starting at
j
, adjust pointeri
leftward until we find the longest prefix wherenums[i] < nums[j]
- This ensures that concatenating the prefix
nums[0...i]
with the suffixnums[j...n-1]
results in a strictly increasing sequence - For each valid
(i, j)
pair, we can remove subarrays starting from any position from 0 toi+1
and ending atj-1
, giving usi + 2
more valid removals - We stop when
nums[j-1] >= nums[j]
, as the suffix would no longer be strictly increasing if we includenums[j-1]
The algorithm efficiently counts all valid incremovable subarrays in O(n)
time using two pointers, avoiding the need to check every possible subarray individually.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with the array nums = [1, 2, 3, 6, 5, 7]
.
Step 1: Find the longest strictly increasing prefix
- Start with
i = 0
- Check:
nums[0] < nums[1]
→1 < 2
✓, soi = 1
- Check:
nums[1] < nums[2]
→2 < 3
✓, soi = 2
- Check:
nums[2] < nums[3]
→3 < 6
✓, soi = 3
- Check:
nums[3] < nums[4]
→6 < 5
✗, stop
The longest strictly increasing prefix is [1, 2, 3, 6]
with i = 3
.
Step 2: Check if entire array is strictly increasing
- Since
i = 3
andn - 1 = 5
, we havei ≠ n - 1
- The array is not entirely strictly increasing, so we continue.
Step 3: Count removals that leave only a prefix (or nothing)
ans = i + 2 = 3 + 2 = 5
These 5 removals are:
- Remove
[5, 7]
(indices 4-5) → Keep[1, 2, 3, 6]
✓ - Remove
[6, 5, 7]
(indices 3-5) → Keep[1, 2, 3]
✓ - Remove
[3, 6, 5, 7]
(indices 2-5) → Keep[1, 2]
✓ - Remove
[2, 3, 6, 5, 7]
(indices 1-5) → Keep[1]
✓ - Remove
[1, 2, 3, 6, 5, 7]
(indices 0-5) → Keep[]
✓
Step 4: Count removals that leave both prefix and suffix
Initialize j = 5
(last index), suffix is [7]
.
First iteration (j = 5):
- Current suffix:
[7]
- Adjust
i
: Check ifnums[3] >= nums[5]
→6 >= 7
✗, soi
stays at 3 - Valid prefix:
[1, 2, 3, 6]
, valid suffix:[7]
- Since
nums[3] < nums[5]
(6 < 7), concatenation gives[1, 2, 3, 6, 7]
✓ - Add
i + 2 = 3 + 2 = 5
to answer ans = 5 + 5 = 10
These 5 additional removals are:
- Remove
[5]
(index 4) → Keep[1, 2, 3, 6, 7]
✓ - Remove
[6, 5]
(indices 3-4) → Keep[1, 2, 3, 7]
✓ - Remove
[3, 6, 5]
(indices 2-4) → Keep[1, 2, 7]
✓ - Remove
[2, 3, 6, 5]
(indices 1-4) → Keep[1, 7]
✓ - Remove
[1, 2, 3, 6, 5]
(indices 0-4) → Keep[7]
✓
Check next iteration:
- Check if
nums[4] >= nums[5]
→5 >= 7
✗ - Move to
j = 4
Second iteration (j = 4):
- Current suffix would be
[5, 7]
- Need to adjust
i
untilnums[i] < nums[4]
→nums[i] < 5
- Check
nums[3] >= nums[4]
→6 >= 5
✓, soi = 2
- Check
nums[2] >= nums[4]
→3 >= 5
✗, soi
stays at 2 - Valid prefix:
[1, 2, 3]
, valid suffix:[5, 7]
- Since
nums[2] < nums[4]
(3 < 5), concatenation gives[1, 2, 3, 5, 7]
✓ - Add
i + 2 = 2 + 2 = 4
to answer ans = 10 + 4 = 14
These 4 additional removals are:
- Remove
[6]
(index 3) → Keep[1, 2, 3, 5, 7]
✓ - Remove
[3, 6]
(indices 2-3) → Keep[1, 2, 5, 7]
✓ - Remove
[2, 3, 6]
(indices 1-3) → Keep[1, 5, 7]
✓ - Remove
[1, 2, 3, 6]
(indices 0-3) → Keep[5, 7]
✓
Check next iteration:
- Check if
nums[3] >= nums[4]
→6 >= 5
✓ - This means extending the suffix further left would break the strictly increasing property
- Stop the algorithm
Final Answer: 14 incremovable subarrays
Solution Implementation
1class Solution:
2 def incremovableSubarrayCount(self, nums: List[int]) -> int:
3 # Find the longest strictly increasing prefix
4 left_boundary = 0
5 array_length = len(nums)
6
7 # Extend the prefix as long as elements are strictly increasing
8 while left_boundary + 1 < array_length and nums[left_boundary] < nums[left_boundary + 1]:
9 left_boundary += 1
10
11 # Special case: entire array is strictly increasing
12 # Any subarray can be removed (including empty subarray)
13 if left_boundary == array_length - 1:
14 return array_length * (array_length + 1) // 2
15
16 # Count subarrays that can be removed from the start
17 # We can remove: nothing, first element, first two elements, ..., up to index left_boundary
18 result = left_boundary + 2
19
20 # Process from the right side to find valid removal ranges
21 right_boundary = array_length - 1
22
23 while right_boundary > 0:
24 # Find the rightmost element in the prefix that is less than current right element
25 # This ensures strict increasing property after removal
26 while left_boundary >= 0 and nums[left_boundary] >= nums[right_boundary]:
27 left_boundary -= 1
28
29 # Add count of valid subarrays ending just before right_boundary
30 # We can remove subarrays from position (left_boundary + 1) to (right_boundary - 1)
31 result += left_boundary + 2
32
33 # Check if we can extend the strictly increasing suffix further left
34 # If not strictly increasing, we stop
35 if nums[right_boundary - 1] >= nums[right_boundary]:
36 break
37
38 right_boundary -= 1
39
40 return result
41
1class Solution {
2 public int incremovableSubarrayCount(int[] nums) {
3 int n = nums.length;
4
5 // Find the longest strictly increasing prefix
6 int prefixEnd = 0;
7 while (prefixEnd + 1 < n && nums[prefixEnd] < nums[prefixEnd + 1]) {
8 prefixEnd++;
9 }
10
11 // If the entire array is strictly increasing, any subarray can be removed
12 // Total subarrays = n*(n+1)/2
13 if (prefixEnd == n - 1) {
14 return n * (n + 1) / 2;
15 }
16
17 // Count valid removable subarrays
18 // Start with subarrays that include everything from index 0 to prefixEnd
19 // Plus removing the entire array (empty prefix case)
20 int validSubarrayCount = prefixEnd + 2;
21
22 // Process suffixes from right to left
23 for (int suffixStart = n - 1; suffixStart > 0; suffixStart--) {
24 // Find the rightmost position in prefix where nums[prefixEnd] < nums[suffixStart]
25 // This ensures the remaining array is strictly increasing after removal
26 while (prefixEnd >= 0 && nums[prefixEnd] >= nums[suffixStart]) {
27 prefixEnd--;
28 }
29
30 // Add count of valid subarrays ending just before suffixStart
31 // prefixEnd + 1 positions in prefix, plus empty prefix case
32 validSubarrayCount += prefixEnd + 2;
33
34 // If suffix is no longer strictly increasing, stop
35 if (nums[suffixStart - 1] >= nums[suffixStart]) {
36 break;
37 }
38 }
39
40 return validSubarrayCount;
41 }
42}
43
1class Solution {
2public:
3 int incremovableSubarrayCount(vector<int>& nums) {
4 int leftIdx = 0;
5 int n = nums.size();
6
7 // Find the longest strictly increasing prefix
8 // Stop when we find a non-increasing pair or reach the end
9 while (leftIdx + 1 < n && nums[leftIdx] < nums[leftIdx + 1]) {
10 ++leftIdx;
11 }
12
13 // If the entire array is strictly increasing
14 // Any subarray can be removed (including empty subarray)
15 // Total count = n + (n-1) + ... + 1 = n*(n+1)/2
16 if (leftIdx == n - 1) {
17 return n * (n + 1) / 2;
18 }
19
20 // Count subarrays that can be removed
21 // Starting with subarrays from index 0 to leftIdx (leftIdx + 1 choices)
22 // Plus removing the entire array (1 choice)
23 int result = leftIdx + 2;
24
25 // Process from right to left, finding valid removal ranges
26 for (int rightIdx = n - 1; rightIdx > 0; --rightIdx) {
27 // Move leftIdx backwards while nums[leftIdx] >= nums[rightIdx]
28 // This ensures after removing elements between leftIdx and rightIdx,
29 // the remaining array is still strictly increasing
30 while (leftIdx >= 0 && nums[leftIdx] >= nums[rightIdx]) {
31 --leftIdx;
32 }
33
34 // Add count of valid subarrays ending before rightIdx
35 // leftIdx + 1 positions to start removal (0 to leftIdx)
36 // Plus removing everything from start to rightIdx-1
37 result += leftIdx + 2;
38
39 // If the suffix is no longer strictly increasing, stop
40 if (nums[rightIdx - 1] >= nums[rightIdx]) {
41 break;
42 }
43 }
44
45 return result;
46 }
47};
48
1/**
2 * Counts the number of incremovable subarrays in the given array.
3 * An incremovable subarray is one that can be removed while keeping the remaining array strictly increasing.
4 *
5 * @param nums - The input array of numbers
6 * @returns The count of incremovable subarrays
7 */
8function incremovableSubarrayCount(nums: number[]): number {
9 const arrayLength: number = nums.length;
10
11 // Find the length of the longest strictly increasing prefix
12 let prefixEndIndex: number = 0;
13 while (prefixEndIndex + 1 < arrayLength && nums[prefixEndIndex] < nums[prefixEndIndex + 1]) {
14 prefixEndIndex++;
15 }
16
17 // If the entire array is strictly increasing, every subarray can be removed
18 // Total subarrays = n * (n + 1) / 2
19 if (prefixEndIndex === arrayLength - 1) {
20 return (arrayLength * (arrayLength + 1)) / 2;
21 }
22
23 // Count subarrays that can be removed from the start
24 // prefixEndIndex + 1 elements form increasing prefix, plus removing entire array
25 let totalCount: number = prefixEndIndex + 2;
26
27 // Check subarrays that can be removed from the end or middle
28 for (let suffixStartIndex: number = arrayLength - 1; suffixStartIndex > 0; suffixStartIndex--) {
29 // Adjust prefix end to maintain strictly increasing property
30 // when connecting prefix with suffix starting at suffixStartIndex
31 while (prefixEndIndex >= 0 && nums[prefixEndIndex] >= nums[suffixStartIndex]) {
32 prefixEndIndex--;
33 }
34
35 // Add count of valid removals:
36 // Can remove from index (prefixEndIndex + 1) to (suffixStartIndex - 1)
37 totalCount += prefixEndIndex + 2;
38
39 // If suffix is not strictly increasing, stop
40 if (nums[suffixStartIndex - 1] >= nums[suffixStartIndex]) {
41 break;
42 }
43 }
44
45 return totalCount;
46}
47
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
.
The algorithm consists of:
- First while loop: Traverses from the beginning of the array until it finds a non-increasing element or reaches the end. This takes at most
O(n)
time. - If the entire array is strictly increasing (checked by
i == n - 1
), it returns immediately withO(1)
calculation. - Second while loop (outer): Starts from
j = n - 1
and decrementsj
. In the worst case, it runsO(n)
times. - Inner while loop: The key insight is that
i
only decrements throughout the entire execution. Sincei
starts at most at positionn-1
and can only decrease to-1
, the total number of decrements across all iterations of the outer loop is bounded byn
. Therefore, the amortized cost isO(1)
per outer loop iteration.
Overall, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Variables
i
,j
,n
, andans
are all integer variables. - No additional data structures are created.
- The space used does not depend on the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors in Counting
One of the most common mistakes is incorrectly calculating the number of valid subarrays for each configuration. When we have a valid prefix ending at index i
, the count should be i + 2
(not i + 1
), because:
- We can remove from index 0 to some point (gives us
i + 1
options) - Plus we can remove everything (empty prefix case)
Incorrect approach:
result = i + 1 # Missing the empty prefix case
Correct approach:
result = i + 2 # Includes all valid removal options
2. Not Handling the Strictly Increasing Check Properly
A critical pitfall is using <=
instead of <
when checking for strictly increasing sequences. The problem requires strictly increasing arrays, meaning no equal consecutive elements.
Incorrect approach:
# Wrong: allows equal elements while left_boundary + 1 < array_length and nums[left_boundary] <= nums[left_boundary + 1]: left_boundary += 1
Correct approach:
# Correct: ensures strictly increasing while left_boundary + 1 < array_length and nums[left_boundary] < nums[left_boundary + 1]: left_boundary += 1
3. Forgetting to Break When Suffix is No Longer Valid
When iterating through suffixes, failing to check if the suffix remains strictly increasing can lead to incorrect counts. Once nums[j-1] >= nums[j]
, extending the suffix leftward would violate the strictly increasing property.
Incorrect approach:
while right_boundary > 0: # ... counting logic ... right_boundary -= 1 # Missing the check for valid suffix extension
Correct approach:
while right_boundary > 0: # ... counting logic ... if nums[right_boundary - 1] >= nums[right_boundary]: break # Stop when suffix can't be extended right_boundary -= 1
4. Misunderstanding the Two-Pointer Logic
A subtle but important pitfall is not understanding why we move left_boundary
backward in the second phase. The pointer left_boundary
needs to be adjusted for each new suffix position to find the maximum valid prefix that can connect with that suffix.
Key insight: For each suffix starting at j
, we need the largest i
such that nums[i] < nums[j]
to maximize the count of valid removals. This is why we only move left_boundary
leftward (never rightward) in the second phase.
5. Edge Case: Single Element Array
While the provided solution handles this correctly, it's worth noting that for a single-element array [x]
, the only subarray is [x]
itself, and removing it leaves an empty array (which is considered strictly increasing). The formula n * (n + 1) // 2
correctly gives us 1 for this case.
In a binary min heap, the minimum element can be found in:
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!