Facebook Pixel

665. Non-decreasing Array

Problem Description

You are given an array nums containing n integers. Your task is to determine whether this array can be made non-decreasing by modifying at most one element.

An array is considered non-decreasing if each element is less than or equal to the next element. Formally, nums[i] <= nums[i + 1] must hold for all valid indices i where 0 <= i <= n - 2.

The key points to understand:

  • You can modify at most one element (change its value to any number)
  • You can also choose to modify zero elements if the array is already non-decreasing
  • After the modification (if any), the entire array should satisfy the non-decreasing property
  • Return true if it's possible to achieve a non-decreasing array with at most one modification, otherwise return false

For example:

  • [4, 2, 3] → Can become [1, 2, 3] or [4, 4, 4] by changing one element, so return true
  • [4, 2, 1] → Cannot become non-decreasing by changing just one element, so return false
  • [1, 2, 3] → Already non-decreasing, so return true
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we scan through the array, we're looking for positions where the non-decreasing property is violated - that is, where nums[i] > nums[i + 1]. If we find more than one such violation, it's impossible to fix the array with just one modification.

The key insight is that when we find a violation at position i (where nums[i] > nums[i + 1]), we have two choices to fix it:

  1. Lower nums[i] to match nums[i + 1] - This makes the current pair valid without affecting future comparisons
  2. Raise nums[i + 1] to match nums[i] - This fixes the current pair but might create issues with nums[i + 1] and nums[i + 2]

Why do we need to try both options? Consider these examples:

  • [3, 4, 2, 5]: At the violation (4 > 2), if we raise 2 to 4, we get [3, 4, 4, 5]
  • [1, 4, 2, 3]: At the violation (4 > 2), raising 2 to 4 gives [1, 4, 4, 3] which is still invalid. But lowering 4 to 2 gives [1, 2, 2, 3]

The strategy is straightforward: iterate through the array until we find the first violation. When found, try both possible fixes and check if either results in a valid non-decreasing array. If we never find a violation, the array is already non-decreasing.

This approach works because if there's exactly one violation, one of these two modifications must work if a solution exists. If neither works, or if we find multiple violations, then it's impossible to fix the array with just one change.

Solution Approach

The implementation follows a clean and efficient approach:

  1. Helper Function for Validation: First, we define an is_sorted helper function that checks if an array is non-decreasing. It uses Python's pairwise function to iterate through consecutive pairs and verifies that each pair satisfies a <= b.

  2. Main Algorithm:

    • Iterate through the array from index 0 to n-2, checking each consecutive pair (nums[i], nums[i+1])

    • When we find the first violation where nums[i] > nums[i+1]:

      Try Option 1: Lower nums[i] to match nums[i+1]

      nums[i] = b  # where b = nums[i+1]
      • Check if the entire array is now sorted using is_sorted(nums)
      • If yes, return True

      Try Option 2: If Option 1 didn't work, raise nums[i+1] to match nums[i]

      nums[i] = nums[i + 1] = a  # where a = original nums[i]
      • This effectively restores nums[i] to its original value and sets nums[i+1] equal to it
      • Check if the array is now sorted and return the result
  3. No Violation Found: If we complete the loop without finding any violation, the array is already non-decreasing, so return True

Time Complexity: O(n) - We iterate through the array at most twice (once in the main loop and once in is_sorted)

Space Complexity: O(1) - We only use a constant amount of extra space (modifying the array in-place)

The elegance of this solution lies in its simplicity - it directly tries both possible fixes when encountering a violation and immediately checks if either works, avoiding complex edge case handling.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the array [3, 4, 2, 5] to demonstrate the solution approach.

Step 1: Initial scan for violations

  • Compare pairs: (3,4) ✓, (4,2) ✗, (2,5) ✓
  • Found violation at index 1: nums[1] = 4 > nums[2] = 2

Step 2: Try Option 1 - Lower nums[1] to match nums[2]

  • Set nums[1] = 2, array becomes [3, 2, 2, 5]
  • Check if sorted: (3,2) ✗ - Not sorted!
  • This option doesn't work

Step 3: Try Option 2 - Raise nums[2] to match nums[1]

  • Restore nums[1] = 4 and set nums[2] = 4
  • Array becomes [3, 4, 4, 5]
  • Check if sorted: (3,4) ✓, (4,4) ✓, (4,5) ✓ - Sorted!
  • Return True

Let's trace through another example: [1, 4, 2, 3]

Step 1: Initial scan

  • Compare pairs: (1,4) ✓, (4,2) ✗, (2,3) ✓
  • Found violation at index 1: nums[1] = 4 > nums[2] = 2

Step 2: Try Option 1 - Lower nums[1]

  • Set nums[1] = 2, array becomes [1, 2, 2, 3]
  • Check if sorted: (1,2) ✓, (2,2) ✓, (2,3) ✓ - Sorted!
  • Return True

For an already sorted array like [1, 2, 3, 4]:

  • Compare all pairs: (1,2) ✓, (2,3) ✓, (3,4) ✓
  • No violations found
  • Return True immediately

This approach efficiently handles all cases by trying both modification strategies only when needed.

Solution Implementation

1class Solution:
2    def checkPossibility(self, nums: List[int]) -> bool:
3        """
4        Check if array can become non-decreasing by modifying at most one element.
5      
6        Args:
7            nums: List of integers to check
8          
9        Returns:
10            True if array can be made non-decreasing with at most one modification
11        """
12      
13        def is_non_decreasing(nums: List[int]) -> bool:
14            """
15            Helper function to check if array is non-decreasing.
16          
17            Args:
18                nums: List of integers to check
19              
20            Returns:
21                True if array is non-decreasing (each element <= next element)
22            """
23            from itertools import pairwise
24            return all(a <= b for a, b in pairwise(nums))
25      
26        n = len(nums)
27      
28        # Iterate through adjacent pairs to find first violation
29        for i in range(n - 1):
30            a, b = nums[i], nums[i + 1]
31          
32            # If we find a decreasing pair (violation of non-decreasing order)
33            if a > b:
34                # Try strategy 1: Lower nums[i] to match nums[i+1]
35                nums[i] = b
36                if is_non_decreasing(nums):
37                    return True
38              
39                # Try strategy 2: Raise nums[i+1] to match nums[i]
40                # First restore nums[i] to original value
41                nums[i] = a
42                nums[i + 1] = a
43                return is_non_decreasing(nums)
44      
45        # No violations found, array is already non-decreasing
46        return True
47
1class Solution {
2    /**
3     * Checks if array can become non-decreasing by modifying at most one element.
4     * 
5     * @param nums The input array to check
6     * @return true if array can become non-decreasing with at most one modification, false otherwise
7     */
8    public boolean checkPossibility(int[] nums) {
9        // Iterate through the array to find the first violation of non-decreasing order
10        for (int i = 0; i < nums.length - 1; i++) {
11            int current = nums[i];
12            int next = nums[i + 1];
13          
14            // Found a violation where current element is greater than next element
15            if (current > next) {
16                // Strategy 1: Try modifying current element to match next element
17                nums[i] = next;
18                if (isSorted(nums)) {
19                    return true;
20                }
21              
22                // Strategy 2: Restore original value and try modifying next element to match current
23                nums[i] = current;
24                nums[i + 1] = current;
25                return isSorted(nums);
26            }
27        }
28      
29        // No violations found, array is already non-decreasing
30        return true;
31    }
32  
33    /**
34     * Helper method to check if array is sorted in non-decreasing order.
35     * 
36     * @param nums The array to check
37     * @return true if array is non-decreasing, false otherwise
38     */
39    private boolean isSorted(int[] nums) {
40        for (int i = 0; i < nums.length - 1; i++) {
41            if (nums[i] > nums[i + 1]) {
42                return false;
43            }
44        }
45        return true;
46    }
47}
48
1class Solution {
2public:
3    bool checkPossibility(vector<int>& nums) {
4        int n = nums.size();
5      
6        // Iterate through the array to find the first violation
7        for (int i = 0; i < n - 1; ++i) {
8            int current = nums[i];
9            int next = nums[i + 1];
10          
11            // Found a violation where current element is greater than next
12            if (current > next) {
13                // Strategy 1: Try modifying nums[i] to match nums[i+1]
14                nums[i] = next;
15                if (is_sorted(nums.begin(), nums.end())) {
16                    return true;
17                }
18              
19                // Strategy 2: Restore nums[i] and try modifying nums[i+1] to match nums[i]
20                nums[i] = current;
21                nums[i + 1] = current;
22                return is_sorted(nums.begin(), nums.end());
23            }
24        }
25      
26        // No violations found, array is already non-decreasing
27        return true;
28    }
29};
30
1/**
2 * Checks if the array can become non-decreasing by modifying at most one element
3 * @param nums - The input array of numbers
4 * @returns true if the array can become non-decreasing with at most one modification, false otherwise
5 */
6function checkPossibility(nums: number[]): boolean {
7    /**
8     * Helper function to check if an array is sorted in non-decreasing order
9     * @param nums - The array to check
10     * @returns true if the array is sorted in non-decreasing order, false otherwise
11     */
12    const isSorted = (nums: number[]): boolean => {
13        for (let i = 0; i < nums.length - 1; i++) {
14            if (nums[i] > nums[i + 1]) {
15                return false;
16            }
17        }
18        return true;
19    };
20  
21    // Iterate through the array to find the first violation of non-decreasing order
22    for (let i = 0; i < nums.length - 1; i++) {
23        const currentElement = nums[i];
24        const nextElement = nums[i + 1];
25      
26        // Found a violation where current element is greater than next element
27        if (currentElement > nextElement) {
28            // Strategy 1: Try modifying the current element to match the next element
29            nums[i] = nextElement;
30            if (isSorted(nums)) {
31                return true;
32            }
33          
34            // Strategy 2: Restore the current element and try modifying the next element instead
35            nums[i] = currentElement;
36            nums[i + 1] = currentElement;
37            return isSorted(nums);
38        }
39    }
40  
41    // No violations found, array is already non-decreasing
42    return true;
43}
44

Time and Space Complexity

Time Complexity: O(n)

The algorithm iterates through the array once with the main loop from i = 0 to n - 2, which takes O(n) time. When a violation is found (where nums[i] > nums[i + 1]), the algorithm performs at most two checks using the is_sorted function. Each call to is_sorted uses the pairwise function which iterates through the array once, taking O(n) time. Since we encounter at most one violation (if more than one exists, we return False), the is_sorted function is called at most twice. Therefore, the overall time complexity is O(n) + 2 * O(n) = O(n).

Space Complexity: O(1)

The algorithm modifies the input array in-place and only uses a constant amount of extra space for variables like i, a, b, and n. The pairwise function in Python creates an iterator that doesn't store the entire paired sequence in memory, using only O(1) additional space. The all() function with a generator expression also operates with O(1) space as it evaluates lazily. Therefore, the total space complexity is O(1).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Modifying the Original Array

The most critical pitfall in this solution is that it modifies the input array in-place. This can be problematic because:

  • The caller might not expect their data to be mutated
  • If the function is called multiple times with the same array, subsequent calls will operate on already-modified data
  • It violates the principle of avoiding side effects in functions

Example of the problem:

nums = [4, 2, 3]
solution = Solution()
result1 = solution.checkPossibility(nums)  # Returns True
print(nums)  # Array has been modified! Could be [2, 2, 3] or [4, 4, 3]
result2 = solution.checkPossibility(nums)  # Operates on modified array

2. Missing Import Statement

The pairwise function from itertools is used inside a nested function, but the import happens within that function. This can cause:

  • Import errors if not handled properly
  • Performance issues if the function is called repeatedly (though Python caches imports)

Solution to Fix These Pitfalls

from itertools import pairwise
from typing import List

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        """
        Check if array can become non-decreasing by modifying at most one element.
      
        Args:
            nums: List of integers to check
          
        Returns:
            True if array can be made non-decreasing with at most one modification
        """
      
        def is_non_decreasing(arr: List[int]) -> bool:
            """Check if array is non-decreasing."""
            return all(a <= b for a, b in pairwise(arr))
      
        n = len(nums)
      
        # Find the first violation
        for i in range(n - 1):
            if nums[i] > nums[i + 1]:
                # Create copies to avoid modifying original array
                # Strategy 1: Try lowering nums[i]
                test_array1 = nums.copy()
                test_array1[i] = nums[i + 1]
                if is_non_decreasing(test_array1):
                    return True
              
                # Strategy 2: Try raising nums[i+1]
                test_array2 = nums.copy()
                test_array2[i + 1] = nums[i]
                return is_non_decreasing(test_array2)
      
        # No violations found
        return True

Alternative Space-Efficient Solution Without Copying: If you want to avoid creating copies but still not modify the original array, you can check the conditions without actually modifying any array:

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        n = len(nums)
        violation_count = 0
      
        for i in range(n - 1):
            if nums[i] > nums[i + 1]:
                violation_count += 1
              
                if violation_count > 1:
                    return False
              
                # Check if modifying nums[i] or nums[i+1] would work
                # without actually modifying the array
                if i > 0 and nums[i - 1] > nums[i + 1]:
                    # Can't lower nums[i] to nums[i+1] because it would violate
                    # the relationship with nums[i-1]
                    if i + 2 < n and nums[i] > nums[i + 2]:
                        # Can't raise nums[i+1] to nums[i] either
                        return False
      
        return True

This alternative approach counts violations and checks feasibility without creating copies or modifying the original array, making it both space-efficient and side-effect free.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More