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: trueThe array never decreases:
1 <= 2 <= 2 <= 3So it is monotone increasing.
Example 2:
Input: nums = [6,5,4,4]
Output: trueThe array never increases:
6 >= 5 >= 4 >= 4So it is monotone decreasing.
Example 3:
Input: nums = [1,3,2]
Output: falseThe array first increases:
1 < 3Then decreases:
3 > 2So it is neither monotone increasing nor monotone decreasing.
First Thought: Check All Pairs
The definition says:
for all i <= jSo 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 = FalseScan 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:
FalseUse:
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:
TrueCorrectness
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 TrueCode Explanation
We track whether the array has gone up or down:
seen_increase = False
seen_decrease = FalseThen 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 = TrueIf the current value is smaller than the previous value, we have seen a decreasing step:
elif nums[i - 1] > nums[i]:
seen_decrease = TrueIf both directions appear, the array cannot be monotonic:
if seen_increase and seen_decrease:
return FalseIf the loop finishes without conflict, the array is monotonic:
return TrueAlternative 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 decreasingThis 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 |