LeetCode 896: Monotonic Array
A clear explanation of checking whether an array is monotonic using one pass and direction flags.
Problem Restatement
We are given an integer array nums.
An array is monotonic if it is either:
| Type | Condition |
|---|---|
| Monotone increasing | For all i <= j, nums[i] <= nums[j] |
| Monotone decreasing | For all i <= j, nums[i] >= nums[j] |
Return true if nums is monotonic. Otherwise, return false.
The array may contain equal adjacent values. Equal values do not break monotonicity. LeetCode gives examples such as [1,2,2,3] and [6,5,4,4] returning true.
Input and Output
| Item | Meaning |
|---|---|
| Input | nums, an integer array |
| Output | true if the array is monotonic |
| Increasing rule | Values never decrease |
| Decreasing rule | Values never increase |
| Equal adjacent values | Allowed |
Function shape:
def isMonotonic(self, nums: list[int]) -> bool:
...
Examples
Example 1:
Input: nums = [1,2,2,3]
Output: true
The array never decreases:
1 <= 2 <= 2 <= 3
So it is monotone increasing.
Example 2:
Input: nums = [6,5,4,4]
Output: true
The array never increases:
6 >= 5 >= 4 >= 4
So it is monotone decreasing.
Example 3:
Input: nums = [1,3,2]
Output: false
The array first increases:
1 < 3
Then decreases:
3 > 2
So it is neither monotone increasing nor monotone decreasing.
First Thought: Check All Pairs
The definition says:
for all i <= j
So a direct idea is to compare every pair of indices.
For monotone increasing, check:
nums[i] <= nums[j]
for every i <= j.
For monotone decreasing, check:
nums[i] >= nums[j]
for every i <= j.
This works, but it does unnecessary work.
If every adjacent pair is ordered correctly, then the whole array is ordered correctly by transitivity.
So we only need to compare neighboring elements.
Key Insight
A monotonic array has at most one direction.
While scanning adjacent pairs:
| Pair relation | Meaning |
|---|---|
nums[i - 1] < nums[i] |
We have seen an increasing step |
nums[i - 1] > nums[i] |
We have seen a decreasing step |
nums[i - 1] == nums[i] |
No direction change |
If we ever see both an increasing step and a decreasing step, the array is not monotonic.
Equal values do not matter because they satisfy both monotone increasing and monotone decreasing conditions.
Algorithm
Initialize two flags:
seen_increase = False
seen_decrease = False
Scan the array from index 1 to the end.
For each adjacent pair:
- If the previous value is smaller, set
seen_increase = True. - If the previous value is larger, set
seen_decrease = True. - If both flags are true, return
False.
If the scan finishes, return True.
Walkthrough
Use:
nums = [1, 3, 2]
Start:
| Flag | Value |
|---|---|
seen_increase |
False |
seen_decrease |
False |
Compare adjacent pairs:
| Pair | Relation | Flags |
|---|---|---|
1, 3 |
Increase | seen_increase = True |
3, 2 |
Decrease | seen_decrease = True |
Now both flags are true.
So the array is not monotonic.
Return:
False
Use:
nums = [6, 5, 4, 4]
Compare adjacent pairs:
| Pair | Relation | Flags |
|---|---|---|
6, 5 |
Decrease | seen_decrease = True |
5, 4 |
Decrease | seen_decrease = True |
4, 4 |
Equal | no change |
We saw only decreasing steps, so the array is monotonic.
Return:
True
Correctness
The algorithm returns False only when it has seen at least one increasing adjacent pair and at least one decreasing adjacent pair.
If both types of adjacent steps exist, the array cannot be monotone increasing because a decreasing adjacent pair violates nums[i - 1] <= nums[i]. It also cannot be monotone decreasing because an increasing adjacent pair violates nums[i - 1] >= nums[i]. Therefore, returning False is correct.
If the algorithm finishes without seeing both types of steps, then one of the following is true:
- It saw only increasing steps and equal steps.
- It saw only decreasing steps and equal steps.
- It saw only equal steps.
In the first case, every adjacent pair satisfies nums[i - 1] <= nums[i], so the array is monotone increasing.
In the second case, every adjacent pair satisfies nums[i - 1] >= nums[i], so the array is monotone decreasing.
In the third case, all values are equal, so the array is both monotone increasing and monotone decreasing.
Thus, returning True is correct.
Complexity
Let:
n = len(nums)
| Metric | Value | Why |
|---|---|---|
| Time | O(n) |
We scan adjacent pairs once |
| Space | O(1) |
We store only two boolean flags |
Implementation
class Solution:
def isMonotonic(self, nums: list[int]) -> bool:
seen_increase = False
seen_decrease = False
for i in range(1, len(nums)):
if nums[i - 1] < nums[i]:
seen_increase = True
elif nums[i - 1] > nums[i]:
seen_decrease = True
if seen_increase and seen_decrease:
return False
return True
Code Explanation
We track whether the array has gone up or down:
seen_increase = False
seen_decrease = False
Then we scan adjacent pairs:
for i in range(1, len(nums)):
If the current value is larger than the previous value, we have seen an increasing step:
if nums[i - 1] < nums[i]:
seen_increase = True
If the current value is smaller than the previous value, we have seen a decreasing step:
elif nums[i - 1] > nums[i]:
seen_decrease = True
If both directions appear, the array cannot be monotonic:
if seen_increase and seen_decrease:
return False
If the loop finishes without conflict, the array is monotonic:
return True
Alternative Implementation
We can also check both monotonic directions directly:
class Solution:
def isMonotonic(self, nums: list[int]) -> bool:
increasing = True
decreasing = True
for i in range(1, len(nums)):
if nums[i - 1] > nums[i]:
increasing = False
if nums[i - 1] < nums[i]:
decreasing = False
return increasing or decreasing
This version is also O(n) time and O(1) space.
Testing
def run_tests():
s = Solution()
assert s.isMonotonic([1, 2, 2, 3]) is True
assert s.isMonotonic([6, 5, 4, 4]) is True
assert s.isMonotonic([1, 3, 2]) is False
assert s.isMonotonic([1]) is True
assert s.isMonotonic([1, 1, 1]) is True
assert s.isMonotonic([1, 2, 3, 2]) is False
assert s.isMonotonic([3, 2, 2, 1]) is True
print("all tests passed")
run_tests()
Test meaning:
| Test | Why |
|---|---|
[1,2,2,3] |
Non-decreasing with equal values |
[6,5,4,4] |
Non-increasing with equal values |
[1,3,2] |
Increase then decrease |
[1] |
Single element is monotonic |
[1,1,1] |
All equal values |
[1,2,3,2] |
Late direction change |
[3,2,2,1] |
Decreasing with equal middle values |