3818. Minimum Prefix Removal to Make Array Strictly Increasing
Problem Description
You are given an integer array nums.
You need to remove exactly one prefix (possibly empty) from nums.
A prefix is a contiguous part of the array that starts from the very beginning. For example, the prefixes of [3, 1, 2] are [] (empty), [3], [3, 1], and [3, 1, 2]. Removing a prefix means deleting those leading elements, leaving behind the rest of the array.
Your goal is to remove a prefix so that the remaining array becomes strictly increasing. An array is strictly increasing if every element is greater than the element before it (i.e., nums[i - 1] < nums[i] for all valid i). Note that an array with zero or one element is always considered strictly increasing.
Return an integer denoting the minimum length of the removed prefix such that the remaining array is strictly increasing.
How We Pick the Algorithm
Why Simulation / Basic DSA?
This problem maps to Simulation / Basic DSA through a short path in the full flowchart.
Scanning the array backward to find the longest strictly increasing suffix and removing the prefix before it is a straightforward reverse traversal.
Open in FlowchartIntuition
The key observation is that we want to keep the longest possible suffix that is strictly increasing, because keeping a longer suffix means removing a shorter prefix.
Think about what makes a suffix strictly increasing. If we look at the array from the end, the suffix stays strictly increasing as long as each element is greater than the one before it. The moment we encounter a pair where nums[i - 1] >= nums[i], the strictly increasing property breaks. This means nums[i - 1] cannot be part of our kept suffix, and neither can anything before it.
So we traverse the array backwards, starting from the last element. As long as consecutive pairs satisfy nums[i - 1] < nums[i], we can safely extend the suffix. The first time we hit a position where nums[i - 1] >= nums[i], we know the suffix must begin at index i. Everything from index 0 to i - 1 must be removed, which is exactly i elements. Therefore, the minimum prefix length to remove is i.
If we make it all the way through without ever breaking the condition, the entire array is already strictly increasing, so we don't need to remove anything and return 0.
Solution Approach
Solution 1: Reverse Traversal
We use a simple reverse traversal to find where the strictly increasing suffix begins.
We iterate the index i from the last element down to the second element, i.e., from len(nums) - 1 to 1. At each step, we compare the current element nums[i] with its previous neighbor nums[i - 1]:
-
If
nums[i - 1] >= nums[i], the strictly increasing condition is broken at this point. This means the suffix can only start at indexi, so all elements from index0toi - 1must be removed. The number of removed elements is exactlyi, so we immediately returni. -
Otherwise, the pair satisfies
nums[i - 1] < nums[i], so we continue extending the suffix toward the front.
If the loop completes without finding any breaking point, the entire array is already strictly increasing, and we return 0.
The reason returning i at the first break point (when scanning from the right) gives the minimum prefix length is that this break point is the rightmost position where the order is violated. Any element to the left of it cannot belong to a strictly increasing suffix, so removing exactly the first i elements is both necessary and sufficient.
The time complexity is O(n), where n is the length of the array nums, since we scan the array at most once. The space complexity is O(1), as we only use a constant amount of extra space.
Example Walkthrough
Let's trace through the solution using a small example.
Input: nums = [5, 3, 4, 6, 2, 7, 8]
We want to find the minimum length prefix to remove so the remaining array is strictly increasing. Following the Reverse Traversal approach, we scan from the last element toward the front, looking for the first place where the strictly increasing order breaks.
We iterate i from len(nums) - 1 = 6 down to 1, comparing nums[i - 1] with nums[i]:
| Step | i | nums[i - 1] | nums[i] | Condition nums[i - 1] >= nums[i]? | Action |
|---|---|---|---|---|---|
| 1 | 6 | nums[5] = 7 | nums[6] = 8 | 7 >= 8? No | Continue extending suffix |
| 2 | 5 | nums[4] = 2 | nums[5] = 7 | 2 >= 7? No | Continue extending suffix |
| 3 | 4 | nums[3] = 6 | nums[4] = 2 | 6 >= 2? Yes | Break found → return i = 4 |
Why we stop at i = 4:
At i = 4, we found nums[3] = 6 >= nums[4] = 2, which violates the strictly increasing property. This is the rightmost violation point. It tells us that the strictly increasing suffix can only begin at index 4.
- The suffix we keep is
nums[4..6] = [2, 7, 8], which is strictly increasing ✓ - The prefix we remove is
nums[0..3] = [5, 3, 4, 6], which is exactly4elements.
Notice that index 3 (value 6) cannot join the suffix because 6 is not less than 2. And anything to the left of index 3 is irrelevant — once the chain breaks at this point, no earlier element can be part of a valid suffix that includes index 4.
Output: 4
This confirms the intuition: by halting at the first break point during the reverse scan, we maximize the length of the kept suffix (3 elements) and therefore minimize the length of the removed prefix (4 elements).
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def minimumPrefixLength(self, nums: List[int]) -> int:
6 # Traverse from the last element toward the front,
7 # comparing each element with its predecessor.
8 for index in range(len(nums) - 1, 0, -1):
9 # If the previous element is not smaller than the current one,
10 # the strictly increasing suffix breaks here.
11 # Return 'index' as the length of the required prefix.
12 if nums[index - 1] >= nums[index]:
13 return index
14
15 # The entire array is already strictly increasing,
16 # so no prefix needs to be removed.
17 return 0
181class Solution {
2 /**
3 * Finds the minimum prefix length such that the element just before the end
4 * of that prefix is greater than or equal to its following element.
5 *
6 * In other words, it scans the array from right to left and returns the
7 * index 'i' where the strictly increasing order from the end is first broken
8 * (i.e., nums[i - 1] >= nums[i]). If the whole array is strictly increasing,
9 * it returns 0.
10 *
11 * @param nums the input integer array
12 * @return the minimum prefix length, or 0 if the array is strictly increasing
13 */
14 public int minimumPrefixLength(int[] nums) {
15 // Traverse the array from the last index down to index 1.
16 for (int i = nums.length - 1; i > 0; i--) {
17 // If the previous element is not smaller than the current element,
18 // the strictly increasing suffix is broken here.
19 if (nums[i - 1] >= nums[i]) {
20 // Return the index 'i' as the minimum prefix length.
21 return i;
22 }
23 }
24 // The entire array is strictly increasing, so no prefix is needed.
25 return 0;
26 }
27}
281class Solution {
2public:
3 int minimumPrefixLength(vector<int>& nums) {
4 int size = nums.size();
5
6 // Traverse from the end of the array toward the front.
7 // We look for the rightmost position where the strictly
8 // increasing property is broken (nums[i - 1] >= nums[i]).
9 for (int i = size - 1; i > 0; --i) {
10 // If the previous element is not smaller than the current one,
11 // the strictly increasing order breaks here.
12 // Returning i means the prefix of length i must be kept.
13 if (nums[i - 1] >= nums[i]) {
14 return i;
15 }
16 }
17
18 // The entire array is already strictly increasing,
19 // so no prefix is required.
20 return 0;
21 }
22};
231/**
2 * Finds the minimum prefix length such that the suffix after it is strictly increasing.
3 *
4 * Scans the array from right to left. The moment a non-increasing pair is found
5 * (where the previous element is greater than or equal to the current one),
6 * the index of the current element is returned as the required prefix length.
7 *
8 * If the whole array is already strictly increasing, returns 0.
9 *
10 * @param nums - The input array of numbers.
11 * @returns The minimum prefix length.
12 */
13function minimumPrefixLength(nums: number[]): number {
14 // Iterate from the last index down to 1 (the loop stops when index reaches 0).
15 for (let index = nums.length - 1; index > 0; --index) {
16 // If the previous element is not smaller than the current element,
17 // the strictly increasing order breaks here.
18 if (nums[index - 1] >= nums[index]) {
19 // The current index equals the length of the required prefix.
20 return index;
21 }
22 }
23
24 // The entire array is strictly increasing, so no prefix is needed.
25 return 0;
26}
27Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the arraynums. The code uses a single loop that iterates from the end of the array towards the beginning, performing at mostn - 1comparisons. In the worst case (when no early return is triggered), the loop runs through nearly the entire array, resulting in linear time complexity. -
Space Complexity:
O(1). The algorithm only uses a constant amount of extra space for the loop variablei, regardless of the input size. No additional data structures are allocated that scale with the input.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Using a Forward Traversal and Returning Too Early
A very common mistake is to scan the array from left to right and return the prefix length at the first violation encountered. The intuition seems reasonable — "find where the order breaks and cut there" — but it produces wrong answers.
Consider nums = [5, 6, 1, 2, 3]:
- A left-to-right scan finds the first break at index
2(sincenums[1] = 6 >= nums[2] = 1). - A naive forward approach might return
2, claiming the suffix[1, 2, 3]is the answer.
In this case the forward answer happens to be correct, but now consider nums = [1, 2, 3, 2, 4]:
- The first break is at index
3(nums[2] = 3 >= nums[3] = 2). - A naive forward approach might return
3, leaving[2, 4]. - But the true answer requires removing the prefix up to the break, leaving the longest strictly increasing suffix. Here
[2, 4]is valid, so length3is correct.
The real danger appears with multiple breaks, such as nums = [3, 1, 2, 1, 5]:
- Forward scan finds the first break at index
1(nums[0] = 3 >= nums[1] = 1) and incorrectly returns1, leaving[1, 2, 1, 5]— which is not strictly increasing. - The correct answer is
3, leaving[1, 5].
Why this happens: The suffix must be strictly increasing from its starting point all the way to the end. The relevant break point is the rightmost one, not the first one. Stopping at the first violation ignores later violations deeper in the array.
Solution: Scan from the right (as in the provided solution). The first violation encountered from the right is the rightmost break point, and returning that index guarantees the entire remaining suffix is strictly increasing.
class Solution:
def minimumPrefixLength(self, nums: List[int]) -> int:
# Correct: scan from the right to find the RIGHTMOST break point.
for index in range(len(nums) - 1, 0, -1):
if nums[index - 1] >= nums[index]:
return index
return 0
Pitfall 2: Misusing the Loop Range and Off-by-One Errors
Two subtle index mistakes frequently occur:
-
Wrong loop bound. Writing
range(len(nums) - 1, -1, -1)(going down to0) causesnums[index - 1]to accessnums[-1]whenindex = 0, silently wrapping around to the last element in Python and producing a false comparison. The loop must stop at1so thatindex - 1is always a valid, non-negative index. -
Returning
index - 1instead ofindex. Some implementations returnindex - 1, reasoning that "the break is betweenindex - 1andindex." But the prefix to remove consists of elements at positions0 .. index - 1, which is exactlyindexelements. Returningindex - 1would leave the offending elementnums[index - 1]in the array.
Solution: Use the bound range(len(nums) - 1, 0, -1) so index - 1 >= 0 always holds, and return index (the count of removed elements), not index - 1. You can verify with the minimal case nums = [2, 1]: the break is at index = 1, and returning 1 correctly leaves [1].
Pitfall 3: Forgetting the Empty-Prefix and Single-Element Cases
If the array is already strictly increasing (or has length 0 or 1), no removal is needed, and the answer is 0. A common error is to initialize the answer to something other than 0 or to assume a removal must always happen.
Solution: Let the loop fall through and return 0 when no violation is found. For arrays of length 0 or 1, range(len(nums) - 1, 0, -1) is empty, so the function correctly returns 0 without any special-casing.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich technique can we use to find the middle of a linked list?
Recommended Readings
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!