1909. Remove One Element to Make the Array Strictly Increasing
Problem Description
You are given a 0-indexed integer array nums
. Your task is to determine if this array can be made strictly increasing by removing exactly one element from it.
A strictly increasing array means that each element is strictly less than the next element. Formally, for an array to be strictly increasing, nums[i-1] < nums[i]
must hold true for every index i
where 1 <= i < nums.length
.
The function should return true
if:
- The array is already strictly increasing (in which case you can remove any element and still have at least one valid strictly increasing sequence), OR
- There exists exactly one element whose removal would make the remaining array strictly increasing
The function should return false
if no single element removal can make the array strictly increasing.
For example:
[1, 2, 10, 5, 7]
→true
(removing 10 gives[1, 2, 5, 7]
which is strictly increasing)[2, 3, 1, 2]
→false
(no single removal can make it strictly increasing)[1, 2, 3]
→true
(already strictly increasing)
The solution works by finding the first position where the strictly increasing property is violated (where nums[i] >= nums[i+1]
), then checking if removing either the element at position i
or position i+1
results in a strictly increasing array. The helper function check(k)
verifies if the array would be strictly increasing after removing the element at index k
.
Intuition
When we need to make an array strictly increasing by removing exactly one element, the key insight is that there can be at most one "bad spot" where the increasing property is violated.
Think about it this way: if the array has two or more places where nums[i] >= nums[i+1]
, then we'd need to remove at least two elements to fix both violations, which exceeds our limit of removing exactly one element.
So our strategy becomes:
- Scan through the array to find the first position where the strictly increasing property breaks (where
nums[i] >= nums[i+1]
) - If no such position exists, the array is already strictly increasing, so we can remove any element and still maintain the property
- If we find such a position
i
, the violation involves two elements:nums[i]
andnums[i+1]
The crucial observation is that to fix this violation, we must remove either nums[i]
or nums[i+1]
- these are the only two candidates that could potentially resolve the issue at this spot. Removing any other element wouldn't fix the violation between these two consecutive elements.
Why only check these two positions? Because:
- If we remove
nums[i]
, we're eliminating the larger element that's causing the violation - If we remove
nums[i+1]
, we're eliminating the smaller element that's breaking the increasing pattern
After removing one of these candidates, we need to verify that the entire remaining array is strictly increasing. We use a helper function that simulates the removal by skipping the element at index k
and checking if all remaining elements form a strictly increasing sequence.
This approach is efficient because we only need to check at most two scenarios (removing element at position i
or i+1
), rather than trying to remove every possible element in the array.
Solution Approach
The implementation consists of two main parts: a helper function check
and the main logic to find the violation point.
Helper Function check(k)
:
This function verifies if the array would be strictly increasing after removing the element at index k
:
- Initialize
pre = -inf
to track the previous element (starting with negative infinity ensures the first element always satisfies the condition) - Iterate through the array with
enumerate
to get both index and value - Skip the element at index
k
(simulating its removal) - For each non-skipped element, check if
pre >= x
. If true, the array isn't strictly increasing, so returnFalse
- Update
pre = x
to track the current element for the next comparison - If we complete the iteration without violations, return
True
Main Algorithm:
-
Find the violation point:
i = 0 while i + 1 < len(nums) and nums[i] < nums[i + 1]: i += 1
This loop advances
i
as long as the strictly increasing property holds. When the loop exits,i
points to:- The last index if the array is already strictly increasing
- The first position where
nums[i] >= nums[i+1]
(the violation)
-
Check two removal candidates:
return check(i) or check(i + 1)
We only need to check if removing either
nums[i]
ornums[i+1]
results in a strictly increasing array:check(i)
: Tests if removing the element at positioni
fixes the issuecheck(i + 1)
: Tests if removing the element at positioni+1
fixes the issue
The function returns
True
if either removal works,False
if neither works.
Time Complexity: O(n)
where n
is the length of the array. We traverse the array once to find the violation point, and the check
function also runs in O(n)
. Since we call check
at most twice, the overall complexity remains O(n)
.
Space Complexity: O(1)
as we only use a constant amount of extra space for variables like i
and pre
.
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 example nums = [1, 2, 10, 5, 7]
to illustrate the solution approach.
Step 1: Find the violation point
We start with i = 0
and check consecutive pairs:
i = 0
: Comparenums[0]
(1) withnums[1]
(2). Since 1 < 2, move to next position.i = 1
: Comparenums[1]
(2) withnums[2]
(10). Since 2 < 10, move to next position.i = 2
: Comparenums[2]
(10) withnums[3]
(5). Since 10 ≥ 5, we found our violation! Stop here.
So i = 2
is our violation point where the strictly increasing property breaks.
Step 2: Check removal candidates
Now we need to check if removing either nums[2]
(10) or nums[3]
(5) makes the array strictly increasing.
Check removing nums[2] (index i = 2):
- Start with
pre = -inf
- Process index 0: value 1. Since -inf < 1, continue. Update
pre = 1
. - Process index 1: value 2. Since 1 < 2, continue. Update
pre = 2
. - Skip index 2 (simulating removal of 10)
- Process index 3: value 5. Since 2 < 5, continue. Update
pre = 5
. - Process index 4: value 7. Since 5 < 7, continue. Update
pre = 7
. - All checks passed! Return
True
.
Since check(2)
returns True
, we don't even need to check check(3)
. The function returns True
because removing element at index 2 (the value 10) results in the strictly increasing array [1, 2, 5, 7]
.
Alternative scenario: If check(2)
had returned False
, we would then check check(3)
to see if removing the element at index 3 (the value 5) would work instead.
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def canBeIncreasing(self, nums: List[int]) -> bool:
6 """
7 Check if the array can become strictly increasing by removing at most one element.
8
9 Args:
10 nums: List of integers to check
11
12 Returns:
13 True if array can be made strictly increasing by removing at most one element
14 """
15
16 def is_strictly_increasing_without_index(skip_index: int) -> bool:
17 """
18 Check if array is strictly increasing when skipping element at given index.
19
20 Args:
21 skip_index: Index of element to skip in the check
22
23 Returns:
24 True if array is strictly increasing after skipping the element
25 """
26 previous_value = -inf # Initialize with negative infinity to handle first element
27
28 for current_index, current_value in enumerate(nums):
29 # Skip the element at skip_index
30 if current_index == skip_index:
31 continue
32
33 # Check if current value maintains strict increasing order
34 if previous_value >= current_value:
35 return False
36
37 previous_value = current_value
38
39 return True
40
41 # Find the first position where strict increasing property is violated
42 violation_index = 0
43 while violation_index + 1 < len(nums) and nums[violation_index] < nums[violation_index + 1]:
44 violation_index += 1
45
46 # Try removing either the element at violation point or the next element
47 # If either results in a strictly increasing array, return True
48 return is_strictly_increasing_without_index(violation_index) or is_strictly_increasing_without_index(violation_index + 1)
49
1class Solution {
2 /**
3 * Determines if the array can be made strictly increasing by removing at most one element.
4 *
5 * @param nums the input array to check
6 * @return true if array can be made strictly increasing by removing at most one element, false otherwise
7 */
8 public boolean canBeIncreasing(int[] nums) {
9 // Find the first position where the strictly increasing property is violated
10 int violationIndex = 0;
11 while (violationIndex + 1 < nums.length && nums[violationIndex] < nums[violationIndex + 1]) {
12 violationIndex++;
13 }
14
15 // Try removing either the element at violation position or the next element
16 // If either removal results in a strictly increasing array, return true
17 return checkStrictlyIncreasing(nums, violationIndex) ||
18 checkStrictlyIncreasing(nums, violationIndex + 1);
19 }
20
21 /**
22 * Checks if the array is strictly increasing after skipping the element at index skipIndex.
23 *
24 * @param nums the array to check
25 * @param skipIndex the index of the element to skip (simulate removal)
26 * @return true if array is strictly increasing after skipping the element, false otherwise
27 */
28 private boolean checkStrictlyIncreasing(int[] nums, int skipIndex) {
29 int previousValue = 0; // Initialize to 0 (smaller than any positive array element)
30
31 for (int i = 0; i < nums.length; i++) {
32 // Skip the element at skipIndex to simulate its removal
33 if (i == skipIndex) {
34 continue;
35 }
36
37 // Check if current element maintains strictly increasing property
38 if (previousValue >= nums[i]) {
39 return false;
40 }
41
42 // Update previous value for next comparison
43 previousValue = nums[i];
44 }
45
46 return true;
47 }
48}
49
1class Solution {
2public:
3 bool canBeIncreasing(vector<int>& nums) {
4 int n = nums.size();
5
6 // Lambda function to check if array is strictly increasing after removing element at index k
7 auto isStrictlyIncreasingWithoutIndex = [&](int indexToSkip) -> bool {
8 int previousValue = 0;
9
10 for (int i = 0; i < n; ++i) {
11 // Skip the element at the specified index
12 if (i == indexToSkip) {
13 continue;
14 }
15
16 // Check if current element maintains strictly increasing order
17 if (previousValue >= nums[i]) {
18 return false;
19 }
20
21 previousValue = nums[i];
22 }
23
24 return true;
25 };
26
27 // Find the first position where the strictly increasing property is violated
28 int violationIndex = 0;
29 while (violationIndex + 1 < n && nums[violationIndex] < nums[violationIndex + 1]) {
30 ++violationIndex;
31 }
32
33 // Check if removing either the element at violation position or the next element
34 // results in a strictly increasing array
35 return isStrictlyIncreasingWithoutIndex(violationIndex) ||
36 isStrictlyIncreasingWithoutIndex(violationIndex + 1);
37 }
38};
39
1/**
2 * Determines if the array can be strictly increasing by removing at most one element
3 * @param nums - Input array of numbers
4 * @returns true if array can be made strictly increasing by removing at most one element
5 */
6function canBeIncreasing(nums: number[]): boolean {
7 const arrayLength: number = nums.length;
8
9 /**
10 * Checks if the array is strictly increasing after removing element at index k
11 * @param indexToSkip - Index of element to skip/remove
12 * @returns true if resulting array is strictly increasing
13 */
14 const isStrictlyIncreasingWithoutIndex = (indexToSkip: number): boolean => {
15 let previousValue: number = 0;
16
17 for (let currentIndex = 0; currentIndex < arrayLength; ++currentIndex) {
18 // Skip the element at the specified index
19 if (currentIndex === indexToSkip) {
20 continue;
21 }
22
23 // Check if current element maintains strictly increasing order
24 if (previousValue >= nums[currentIndex]) {
25 return false;
26 }
27
28 previousValue = nums[currentIndex];
29 }
30
31 return true;
32 };
33
34 // Find the first index where the strictly increasing property is violated
35 let violationIndex: number = 0;
36 while (violationIndex + 1 < arrayLength && nums[violationIndex] < nums[violationIndex + 1]) {
37 ++violationIndex;
38 }
39
40 // Try removing either the element at violation index or the next element
41 // If either results in a strictly increasing array, return true
42 return isStrictlyIncreasingWithoutIndex(violationIndex) ||
43 isStrictlyIncreasingWithoutIndex(violationIndex + 1);
44}
45
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
.
The algorithm first iterates through the array to find the first position i
where nums[i] >= nums[i+1]
, which takes O(n)
time in the worst case. Then it calls the check
function at most twice - check(i)
and check(i+1)
. Each call to check
iterates through the entire array once (skipping one element), which takes O(n)
time. Therefore, the total time complexity is O(n) + 2 * O(n) = O(n)
.
Space Complexity: O(1)
.
The algorithm only uses a constant amount of extra space for variables like i
, pre
, and loop counters. No additional data structures that scale with input size are created. The check
function is defined within the method but doesn't create any space that depends on the input size - it only uses constant space for its local variables.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Handling Multiple Violations
The Problem: A common mistake is trying to handle cases where there are multiple violations in the array. Beginners often write code that attempts to fix all violations or counts the number of violations:
# INCORRECT approach
def canBeIncreasing(self, nums: List[int]) -> bool:
violations = 0
for i in range(len(nums) - 1):
if nums[i] >= nums[i + 1]:
violations += 1
# Try to "fix" by modifying values - WRONG!
if i > 0 and nums[i - 1] >= nums[i + 1]:
nums[i + 1] = nums[i]
return violations <= 1
Why It Fails:
- Simply counting violations doesn't tell us if removing one element will fix the problem
- Modifying array values changes the problem constraints
- Example:
[1, 2, 10, 5, 7]
has one violation at index 2, but we need to know WHETHER removing index 2 OR index 3 will work
The Solution: Focus on finding just the FIRST violation point and only check those two specific removal candidates:
# Find FIRST violation only
i = 0
while i + 1 < len(nums) and nums[i] < nums[i + 1]:
i += 1
# Only check these two specific removals
return check(i) or check(i + 1)
Pitfall 2: Not Considering Adjacent Elements After Removal
The Problem: When checking if removing an element works, forgetting that the elements before and after the removed element become adjacent:
# INCORRECT check function
def check(k):
for i in range(len(nums)):
if i == k:
continue
if i > 0 and i - 1 != k and nums[i - 1] >= nums[i]:
return False
return True
Why It Fails:
This doesn't properly track the "previous" element after skipping. For example, with [1, 5, 3, 4]
:
- If we remove index 1 (value 5), we need to check if 1 < 3 < 4
- The incorrect approach might miss comparing 1 with 3
The Solution: Use a separate variable to track the actual previous element in the resulting array:
def check(k):
pre = -inf # Track the actual previous element
for i, x in enumerate(nums):
if i == k:
continue
if pre >= x: # Compare with tracked previous
return False
pre = x # Update tracked previous
return True
Pitfall 3: Edge Case with Already Strictly Increasing Arrays
The Problem: Not handling the case where the array is already strictly increasing:
# INCORRECT: Assumes there must be a violation
def canBeIncreasing(self, nums: List[int]) -> bool:
for i in range(len(nums) - 1):
if nums[i] >= nums[i + 1]:
# Found violation, check removals
return check(i) or check(i + 1)
# Forgot to handle no violations case!
return False # WRONG!
Why It Fails:
If the array is already strictly increasing (like [1, 2, 3, 4]
), there are no violations. The function should return True
because we can remove any element and still maintain a strictly increasing sequence.
The Solution:
The correct approach handles this naturally by having i
point to the last index when no violations are found:
i = 0
while i + 1 < len(nums) and nums[i] < nums[i + 1]:
i += 1
# If no violation, i = len(nums) - 1
# check(len(nums) - 1) will return True for already increasing arrays
return check(i) or check(i + 1)
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
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 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
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 represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!