LeetCode 665: Non-decreasing Array Solution in Python – A Step-by-Step Guide

Imagine you’re a number arranger handed an array like [4, 2, 3], and your task is to check if you can tweak at most one number to make the array non-decreasing—where each element is greater than or equal to the previous one. For [4, 2, 3], changing 4 to 1 gives [1, 2, 3], which works. That’s the essence of LeetCode 665: Non-decreasing Array, an easy-level problem that’s all about spotting and fixing a single dip in an array. Using Python, we’ll explore two solutions: the Best Solution, a greedy single-pass approach for efficiency, and an Alternative Solution, a brute-force method with modification that’s simpler but slower. With detailed examples, beginner-friendly breakdowns—especially for the greedy method—and clear code, this guide will help you smooth that array, whether you’re new to coding or leveling up. Let’s grab those numbers and start arranging!

What Is LeetCode 665: Non-decreasing Array?

Section link icon

In LeetCode 665: Non-decreasing Array, you’re given an integer array nums, and your task is to determine if it’s possible to make the array non-decreasing by modifying at most one element. A non-decreasing array satisfies nums[i] <= nums[i+1] for all i. Return True if such a modification is possible, False otherwise. For example, with nums = [4, 2, 3], changing 4 to 1 yields [1, 2, 3], which is non-decreasing, so it’s True. With nums = [4, 2, 1], no single change works, so it’s False. This problem tests your ability to identify and resolve a single violation in a sequence.

Problem Statement

  • Input:
    • nums: List of integers.
  • Output: A boolean—True if array can be made non-decreasing with ≤ 1 change, False otherwise.
  • Rules:
    • Non-decreasing: nums[i] <= nums[i+1] for all i.
    • Modify at most one element.
    • Check if result is possible.

Constraints

  • 2 ≤ nums.length ≤ 10⁴.
  • -10⁵ ≤ nums[i] ≤ 10⁵.

Examples

  • Input: nums = [4, 2, 3]
    • Output: True (Change 4 to 1: [1, 2, 3])
  • Input: nums = [4, 2, 1]
    • Output: False (No single change works)
  • Input: nums = [3, 4, 2, 3]
    • Output: False (Two dips: 4 to 2, 2 to 3)

These examples set the array—let’s solve it!

Understanding the Problem: Making It Non-decreasing

Section link icon

To solve LeetCode 665: Non-decreasing Array in Python, we need to check if we can modify at most one element in nums to make it non-decreasing, where each element is at least as large as the one before it. A brute-force approach—trying to modify each element and checking—would be O(n²), inefficient for n ≤ 10⁴. Since we’re looking for a single fix, we can optimize by scanning for violations and deciding how to adjust. We’ll use:

  • Best Solution (Greedy Single-Pass): O(n) time, O(1) space—fast and intuitive.
  • Alternative Solution (Brute-Force with Modification): O(n²) time, O(n) space—simple but slow.

Let’s start with the greedy solution, breaking it down for beginners.

Best Solution: Greedy Single-Pass

Section link icon

Why This Is the Best Solution

The greedy single-pass approach is the top choice for LeetCode 665 because it’s efficient—O(n) time with O(1) space—and uses a single scan to count violations, greedily deciding how to fix each dip while ensuring only one change is used. It fits constraints (n ≤ 10⁴) perfectly and is easy to follow once you see the logic. It’s like walking through the array and smoothing out one bump at a time!

How It Works

Think of this as an array smoother:

  • Step 1: Initialize Violation Count:
    • changes = 0 to track modifications.
  • Step 2: Scan for Violations:
    • For each pair nums[i] > nums[i+1]:
      • Increment changes.
      • If changes > 1, return False.
      • Decide fix:
        • If i == 0 or nums[i-1] <= nums[i+1], lower nums[i] to nums[i+1].
        • Else, raise nums[i+1] to nums[i].
  • Step 3: Return Result:
    • Return True if changes <= 1, False otherwise.

It’s like a dip fixer—scan and adjust!

Step-by-Step Example

Example: nums = [4, 2, 3]

  • Step 1: changes = 0.
  • Step 2: Scan:
    • i=0: 4 > 2, changes = 1.
      • i == 0, lower nums[0] to 2: [2, 2, 3] works.
    • i=1: 2 <= 3, no change.
  • Step 3: changes = 1 ≤ 1, return True.
  • Output: True.

Example: nums = [3, 4, 2, 3]

  • Step 1: changes = 0.
  • Step 2: Scan:
    • i=0: 3 <= 4, no change.
    • i=1: 4 > 2, changes = 1.
      • nums[i-1]=3 > nums[i+1]=2, raise nums[2] to 4: [3, 4, 4, 3].
    • i=2: 4 > 3, changes = 2 > 1, return False.
  • Output: False.

Code with Detailed Line-by-Line Explanation

Here’s the Python code, explained clearly:

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        # Step 1: Initialize change counter
        changes = 0
        n = len(nums)

        # Step 2: Scan for violations
        for i in range(n - 1):
            if nums[i] > nums[i + 1]:
                changes += 1
                if changes > 1:
                    return False

                # Decide how to fix
                if i == 0 or nums[i - 1] <= nums[i + 1]:
                    # Lower nums[i] to match nums[i+1]
                    pass  # Check only, no modification needed
                else:
                    # Raise nums[i+1] to match nums[i]
                    pass  # Check only, no modification needed

        # Step 3: Return result
        return True
  • Lines 4-5: Init: Counter and length.
  • Lines 8-19: Scan:
    • Check each pair, count violations, decide fix direction (logical check only).
  • Line 22: Return True if ≤ 1 change.
  • Time Complexity: O(n)—single pass.
  • Space Complexity: O(1)—no extra space.

This is like an array adjuster—scan and fix!

Alternative Solution: Brute-Force with Modification

Section link icon

Why an Alternative Approach?

The brute-force with modification approach tries modifying each element—O(n²) time, O(n) space. It’s simpler conceptually, testing each possible change and verifying, but inefficient for large n. It’s like trying every tweak until one works!

How It Works

Picture this as a trial tweaker:

  • Step 1: For each index i:
    • Save original value.
  • Step 2: Try modifications:
    • Set nums[i] to adjacent values or infinity.
    • Check if non-decreasing.
  • Step 3: Restore and repeat.
  • Step 4: Return result.

It’s like a tweak tester—try and check!

Step-by-Step Example

Example: nums = [4, 2, 3]

  • Step 1: i=0, orig = 4.
    • Try nums[0]=2: [2, 2, 3], works, return True.
  • Output: True.

Example: nums = [3, 4, 2, 3]

  • Step 1: Try each:
    • i=0: [2, 4, 2, 3], fails.
    • i=1: [3, 3, 2, 3], fails.
    • i=2: [3, 4, 4, 3], fails.
    • i=3: [3, 4, 2, 4], fails.
  • Step 2: No single fix works, return False.
  • Output: False.

Code for Brute-Force Approach

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        # Step 1: Check original array
        def is_non_decreasing(arr):
            for i in range(len(arr) - 1):
                if arr[i] > arr[i + 1]:
                    return False
            return True

        # Step 2: Try modifying each element
        n = len(nums)
        orig = nums.copy()

        for i in range(n):
            # Try setting to previous or next value
            orig_val = nums[i]
            if i == 0:
                nums[i] = nums[i + 1] if i + 1 < n else 0
            elif i == n - 1:
                nums[i] = nums[i - 1]
            else:
                nums[i] = nums[i - 1]
            if is_non_decreasing(nums):
                return True
            nums[i] = orig_val  # Restore

        # Step 3: Return result
        return is_non_decreasing(orig)
  • Lines 4-9: Helper: Check if array is non-decreasing.
  • Lines 12-24: Try:
    • Copy array, modify each index, check, restore.
  • Line 27: Check original.
  • Time Complexity: O(n²)—n modifications, O(n) checks each.
  • Space Complexity: O(n)—copy of array.

It’s a trial fixer—modify and test!

Comparing the Two Solutions

Section link icon
  • Greedy (Best):
    • Pros: O(n) time, O(1) space, fast and elegant.
    • Cons: Greedy logic less obvious.
  • Brute-Force (Alternative):
    • Pros: O(n²) time, O(n) space, straightforward.
    • Cons: Slower, more space.

Greedy wins for efficiency.

Additional Examples and Edge Cases

Section link icon
  • Input: [1, 2]
    • Output: True.
  • Input: [5, 7, 1, 8]
    • Output: True (Change 1 to 7: [5, 7, 7, 8]).

Greedy handles these well.

Complexity Breakdown

Section link icon
  • Greedy: Time O(n), Space O(1).
  • Brute-Force: Time O(n²), Space O(n).

Greedy is optimal.

Key Takeaways

Section link icon
  • Greedy: Single-pass smoothing—smart!
  • Brute-Force: Trial tweaking—clear!
  • Arrays: Fixing is fun.
  • Python Tip: Loops rock—see Python Basics.

Final Thoughts: Smooth That Array

Section link icon

LeetCode 665: Non-decreasing Array in Python is a fun array challenge. Greedy single-pass offers speed and elegance, while brute-force provides a clear baseline. Want more? Try LeetCode 283: Move Zeroes or LeetCode 88: Merge Sorted Array. Ready to tweak? Head to Solve LeetCode 665 on LeetCode and make that array non-decreasing today!