Count the Number of Incremovable Subarrays II
HardPrompt
You are given a 0-indexed array of positive integers nums.
A subarray of nums is called incremovable if nums becomes strictly increasing on removing the subarray. For example, the subarray [3, 4] is an incremovable subarray of [5, 3, 4, 6, 7] because removing this subarray changes the array [5, 3, 4, 6, 7] to [5, 6, 7] which is strictly increasing.
Return the total number of incremovable subarrays of nums.
Note that an empty array is considered strictly increasing.
A subarray is a contiguous non-empty sequence of elements within an array.
Example 1:
Input: nums = [1,2,3,4]
Output: 10
Explanation: The 10 incremovable subarrays are: [1], [2], [3], [4], [1,2], [2,3], [3,4], [1,2,3], [2,3,4], and [1,2,3,4], because on removing any one of these subarrays nums becomes strictly increasing. Note that you cannot select an empty subarray.Example 2:
Input: nums = [6,5,7,8]
Output: 7
Explanation: The 7 incremovable subarrays are: [5], [6], [5,7], [6,5], [5,7,8], [6,5,7] and [6,5,7,8].
It can be shown that there are only 7 incremovable subarrays in nums.Example 3:
Input: nums = [8,7,6,6]
Output: 3
Explanation: The 3 incremovable subarrays are: [8,7,6], [7,6,6], and [8,7,6,6]. Note that [8,7] is not an incremovable subarray because after removing [8,7] nums becomes [6,6], which is sorted in ascending order but not strictly increasing.
Constraints:
1 <= nums.length <= 1051 <= nums[i] <= 109
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through all possible subarrays nums[i..j] and, for each one, checks if removing it results in a strictly increasing array. To avoid re-computing whether the remaining parts are sorted for each subarray, we can precompute which prefixes and suffixes of the original array are strictly increasing. This brings the check for each subarray down to O(1), but the overall complexity remains quadratic due to iterating through all subarrays.
Algorithm
- Precomputation:
- Create a boolean array
is_prefix_sortedof sizen.is_prefix_sorted[i]will be true if the subarraynums[0...i]is strictly increasing. - Create a boolean array
is_suffix_sortedof sizen.is_suffix_sorted[j]will be true if the subarraynums[j...n-1]is strictly increasing. - These arrays can be filled in O(n) time.
- Create a boolean array
- Main Logic:
- Initialize a counter
countto 0. - Iterate through all possible start indices
ifrom0ton. - For each
i, iterate through all possible end indicesjfromi-1ton-1.- The subarray to be removed is
nums[i...j]. Ifi > j, the removed subarray is empty, which is not allowed.
- The subarray to be removed is
- For each non-empty subarray
nums[i...j]:- Check if the prefix
nums[0...i-1]is strictly increasing using the precomputed array. - Check if the suffix
nums[j+1...n-1]is strictly increasing using the precomputed array. - If both prefix and suffix are non-empty (i.e.,
i > 0andj < n-1), check ifnums[i-1] < nums[j+1]. - If all conditions are met, increment the
count.
- Check if the prefix
- Initialize a counter
- Return
count.
Walkthrough
A subarray nums[i..j] is considered 'incremovable' if the array formed by concatenating the prefix nums[0...i-1] and the suffix nums[j+1...n-1] is strictly increasing. This holds true if three conditions are met:
- The prefix
nums[0...i-1]is strictly increasing. - The suffix
nums[j+1...n-1]is strictly increasing. - If both the prefix and suffix are non-empty, the last element of the prefix must be strictly less than the first element of the suffix (i.e.,
nums[i-1] < nums[j+1]).
To implement this efficiently, we first precompute boolean arrays that tell us if nums[0...k] and nums[k...n-1] are sorted for all k. Then, we can loop through all O(n^2) subarrays and check these conditions in constant time.
class Solution { public long incremovableSubarrayCount(int[] nums) { int n = nums.length; long count = 0; // Find the longest strictly increasing prefix int l = 0; while (l + 1 < n && nums[l] < nums[l + 1]) { l++; } // Find the longest strictly increasing suffix int r = n - 1; while (r > 0 && nums[r - 1] < nums[r]) { r--; } for (int i = 0; i < n; i++) { // start index of subarray to remove for (int j = i; j < n; j++) { // end index of subarray to remove // Check if prefix nums[0...i-1] is sorted boolean prefixOk = (i - 1 <= l); // Check if suffix nums[j+1...n-1] is sorted boolean suffixOk = (j + 1 >= r); if (prefixOk && suffixOk) { boolean connectionOk = true; if (i > 0 && j < n - 1) { if (nums[i - 1] >= nums[j + 1]) { connectionOk = false; } } if (connectionOk) { count++; } } } } return count; }}Complexity
Time
O(n^2). The nested loops iterate through all `n * (n + 1) / 2` possible subarrays, and the check inside is O(1).
Space
O(1). The implementation shown avoids explicit precomputation arrays by finding the bounds `l` and `r`, thus using constant extra space.
Trade-offs
Pros
Conceptually simple and directly follows the problem definition.
Easier to implement correctly compared to more optimized solutions.
Cons
The time complexity of O(n^2) is too slow for the given constraints (n <= 10^5) and will result in a 'Time Limit Exceeded' error on most platforms.
A more efficient approach avoids checking every subarray. We can observe that for a subarray nums[i..j] to be incremovable, the prefix nums[0..i-1] must itself be a strictly increasing prefix of nums, and nums[j+1..n-1] must be a strictly increasing suffix. This observation allows us to limit our search space.
We first find the longest strictly increasing prefix (ending at index l) and the longest strictly increasing suffix (starting at index r). Then, we iterate through all valid prefixes (which are prefixes of nums[0..l]) and for each, we count how many valid suffixes (which are suffixes of nums[r..n-1]) can be concatenated to it. This count can be done efficiently using binary search because the suffix nums[r..n-1] is sorted.
Algorithm
- Find Boundaries:
- Find
l, the end index of the longest strictly increasing prefix ofnums. - Find
r, the start index of the longest strictly increasing suffix ofnums.
- Find
- Handle Sorted Case:
- If
l >= r, the entire array is already strictly increasing. Any subarray removal will result in a strictly increasing array. The total number of non-empty subarrays isn * (n + 1) / 2. Return this value.
- If
- Count Incremovable Subarrays:
- Initialize
count. Start by counting removals that leave an empty prefix. This occurs when we removenums[0...j]. The remaining suffixnums[j+1...n-1]is valid ifj+1 >= r. This givesn-r+1such subarrays. So,count = n - r + 1. - Iterate through each valid non-empty prefix, which are
nums[0...i]forifrom0tol. - For each prefix, find how many valid suffixes can follow it. A suffix starting at
pis valid ifp >= randnums[p] > nums[i]. - Since
nums[r...n-1]is sorted, we can use binary search to find the first indexp >= rwherenums[p] > nums[i]. - The number of valid non-empty suffixes is
n - p. Adding the case for an empty suffix, we have(n - p) + 1valid choices for the remaining part. - Add this number to
count.
- Initialize
- Return
count.
Walkthrough
The core idea is to count pairs of valid prefixes and suffixes that can form a strictly increasing sequence when concatenated. A prefix nums[0...i-1] is valid if i-1 <= l, and a suffix nums[j+1...n-1] is valid if j+1 >= r.
We can systematically count the total number of incremovable subarrays by considering two disjoint cases:
- Removing a subarray that leaves an empty prefix: This means removing
nums[0...j]. The remaining partnums[j+1...n-1]must be a valid strictly increasing suffix. This is true ifj+1 >= r. The number of suchj's isn-r+1. - Removing a subarray that leaves a non-empty prefix: The prefix must be
nums[0...i]for some0 <= i <= l. For each such prefix, we need to count the number of valid suffixesnums[p...n-1](p >= r) such thatnums[i] < nums[p]. Sincenums[r...n-1]is sorted, we can find the first validpusing binary search. Ifpis the first index, then all suffixes starting frompton-1are valid, plus the empty suffix. This gives(n-p) + 1possibilities for eachi.
class Solution { public long incremovableSubarrayCount(int[] nums) { int n = nums.length; int l = 0; while (l + 1 < n && nums[l] < nums[l + 1]) { l++; } if (l == n - 1) { return (long)n * (n + 1) / 2; } int r = n - 1; while (r > 0 && nums[r - 1] < nums[r]) { r--; } // Case 1: Removing a subarray starting at index 0 (empty prefix left). // The remaining suffix must be strictly increasing, so it must start at or after r. // This means we remove nums[0...j] where j+1 >= r, so j >= r-1. // j can be r-1, ..., n-1. This gives n-r+1 subarrays. long count = n - r + 1; // Case 2: Removing a subarray leaving a non-empty prefix nums[0...i] where 0 <= i <= l. for (int i = 0; i <= l; i++) { int target = nums[i]; // Binary search for the first element in nums[r...n-1] > target. int low = r, high = n - 1; int p = n; // This will be the start index of the valid suffix part. while (low <= high) { int mid = low + (high - low) / 2; if (nums[mid] > target) { p = mid; high = mid - 1; } else { low = mid + 1; } } // Valid suffixes start from p, p+1, ..., n-1. And the empty suffix. // Total valid suffix choices: (n - p) + 1. count += (n - p) + 1; } return count; }}Complexity
Time
O(n log n). Finding `l` and `r` takes O(n). The main loop runs up to `n` times, and each iteration performs a binary search on a portion of the array, which takes O(log n) time.
Space
O(1), as we only use a few variables to store indices and the count.
Trade-offs
Pros
Much more efficient than the brute-force approach.
Correctly solves the problem within the time limits for the given constraints.
Cons
While it passes the constraints, it's not the most optimal solution.
The repeated binary searches introduce a logarithmic factor to the time complexity.
This approach is the most optimal one and builds upon the previous method. We can optimize the process of finding valid suffix partners for each prefix. In the O(n log n) approach, we performed a binary search for each prefix. However, we can notice that as we iterate through the prefixes nums[0...i] for i=0, 1, ..., l, the value of nums[i] is strictly increasing. This means the starting point of the valid suffix we are looking for will also be non-decreasing.
This monotonicity allows us to use a two-pointer technique. One pointer i iterates through the prefix elements nums[0...l], and another pointer j scans the suffix elements nums[r...n-1]. Since j never needs to move backward, the search for all prefixes can be done in a single combined pass, leading to a linear time solution.
Algorithm
- Find Boundaries:
- Find
l, the end index of the longest strictly increasing prefix. - Find
r, the start index of the longest strictly increasing suffix.
- Find
- Handle Sorted Case:
- If
l >= r, the array is sorted. Returnn * (n + 1) / 2.
- If
- Count using Two Pointers:
- Initialize
count = n - r + 1. This accounts for all removals that leave an empty prefix. - Initialize a second pointer
j = r. - Use a primary pointer
ito iterate through the valid prefixes, fromi = 0tol. - For each
i,nums[i]is the last element of the current prefix. We need to find the first element innums[r...n-1]that is greater thannums[i]. - Instead of a new search, we advance the
jpointer from its last position:while (j < n && nums[j] <= nums[i]) { j++; }. - After the
whileloop,jis the index of the first element in the suffix part that is larger thannums[i]. The number of valid suffixes is(n - j)(non-empty)+ 1(empty). - Add
n - j + 1to thecount.
- Initialize
- Return
count.
Walkthrough
The overall strategy is the same: find l and r, then count valid prefix-suffix pairings. The optimization comes from how we count the pairings for non-empty prefixes.
We use a pointer i to iterate from 0 to l, representing the end of the prefix nums[0...i]. We use another pointer j, initialized to r, to find the start of a valid suffix. For each i, we advance j just enough to find the first element nums[j] that is greater than nums[i]. Since nums[i] increases as i increases, j will only ever move forward. The total number of steps taken by i and j combined is at most l + (n-r), which is O(n).
class Solution { public long incremovableSubarrayCount(int[] nums) { int n = nums.length; int l = 0; while (l + 1 < n && nums[l] < nums[l + 1]) { l++; } // If the whole array is strictly increasing, any subarray is incremovable. if (l == n - 1) { return (long)n * (n + 1) / 2; } int r = n - 1; while (r > 0 && nums[r - 1] < nums[r]) { r--; } // Case 1: Removing a subarray that leaves an empty prefix. // This means removing nums[0...j]. The remaining part is nums[j+1...n-1]. // This must be a strictly increasing suffix, so j+1 >= r, which means j >= r-1. // j can be r-1, r, ..., n-1. This gives (n-1) - (r-1) + 1 = n-r+1 subarrays. long count = n - r + 1; // Case 2: Removing a subarray that leaves a non-empty prefix nums[0...i] where 0 <= i <= l. int j = r; for (int i = 0; i <= l; i++) { int target = nums[i]; // Use the second pointer j to find the first element in the suffix part > target. // We can continue the search from j's previous position due to monotonicity. while (j < n && nums[j] <= target) { j++; } // For the prefix ending at nums[i], any suffix starting from nums[j] is valid. // The number of valid non-empty suffixes is n - j. // We also add 1 for the case of an empty suffix (removing up to the end). count += (n - j) + 1; } return count; }}Complexity
Time
O(n). Finding `l` and `r` takes O(n). The two-pointer traversal also takes O(n) because each pointer (`i` and `j`) traverses its respective part of the array at most once.
Space
O(1). The solution uses a constant amount of extra space.
Trade-offs
Pros
Provides the most optimal time complexity of O(n).
Highly efficient and scales well for large inputs.
Cons
The logic can be subtle to get right, especially with pointer initialization and loop conditions.
Solutions
Solution
class Solution {public long incremovableSubarrayCount(int[] nums) { int i = 0, n = nums.length; while (i + 1 < n && nums[i] < nums[i + 1]) { ++i; } if (i == n - 1) { return n * (n + 1L) / 2; } long ans = i + 2; for (int j = n - 1; j > 0; --j) { while (i >= 0 && nums[i] >= nums[j]) { --i; } ans += i + 2; if (nums[j - 1] >= nums[j]) { break; } } return ans; }}class Solution {public: long long incremovableSubarrayCount(vector<int> &nums) { int i = 0, n = nums.size(); while (i + 1 < n && nums[i] < nums[i + 1]) { ++i; } if (i == n - 1) { return n * (n + 1LL) / 2; } long long ans = i + 2; for (int j = n - 1; j > 0; --j) { while (i >= 0 && nums[i] >= nums[j]) { --i; } ans += i + 2; if (nums[j - 1] >= nums[j]) { break; } } return ans; }};class Solution: def incremovableSubarrayCount(self, nums: List[int]) -> int: i, n = 0, len(nums) while i + 1 < n and nums[i] < nums[i + 1]: i += 1 if i == n - 1: return n * (n + 1) // 2 ans = i + 2 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 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.