896. Monotonic Array
Problem Description
You need to determine if an array is monotonic. An array is considered monotonic if it follows one of these two patterns:
-
Monotone Increasing: Every element is less than or equal to the next element. In other words, for any two indices
i
andj
wherei <= j
, we havenums[i] <= nums[j]
. -
Monotone Decreasing: Every element is greater than or equal to the next element. In other words, for any two indices
i
andj
wherei <= j
, we havenums[i] >= nums[j]
.
Given an integer array nums
, you should return true
if the array is either monotone increasing or monotone decreasing. Return false
if the array doesn't follow either pattern.
For example:
[1, 2, 2, 3]
is monotonic (monotone increasing)[6, 5, 4, 4]
is monotonic (monotone decreasing)[1, 3, 2]
is not monotonic (neither consistently increasing nor decreasing)
The solution checks both conditions by examining consecutive pairs of elements. It uses pairwise(nums)
to get all adjacent pairs (a, b)
and verifies if all pairs satisfy the increasing condition (a <= b
) or all pairs satisfy the decreasing condition (a >= b
). If either condition is fully satisfied, the array is monotonic.
Intuition
The key insight is that a monotonic array must be entirely non-decreasing or entirely non-increasing. We can't have some parts going up and other parts going down.
Think about what makes an array monotonic - it needs to maintain a consistent direction throughout. If we examine each consecutive pair of elements, they should all follow the same pattern:
- For increasing: every pair
(a, b)
should satisfya <= b
- For decreasing: every pair
(a, b)
should satisfya >= b
Instead of trying to detect which type of monotonic pattern we have first and then checking it, we can simply check both patterns simultaneously. Why? Because if an array is monotonic, it will completely satisfy at least one of these conditions.
The elegant approach is to:
- Check if all consecutive pairs are non-decreasing (
a <= b
for every pair) - Check if all consecutive pairs are non-increasing (
a >= b
for every pair) - If either check passes completely, the array is monotonic
This works because:
- If the array is monotone increasing, the first check will return
True
- If the array is monotone decreasing, the second check will return
True
- If the array is neither (has mixed increases and decreases), both checks will return
False
Using pairwise(nums)
gives us a clean way to iterate through consecutive pairs (nums[i], nums[i+1])
, and the all()
function ensures that every single pair meets the condition. The final or
operation captures the fact that we only need one of the two patterns to be satisfied.
Solution Approach
The implementation uses a single traversal approach to determine if the array is monotonic. Here's how the solution works:
-
Check for Monotone Increasing Pattern:
- Use
pairwise(nums)
to generate consecutive pairs from the array - For each pair
(a, b)
, check ifa <= b
- The
all()
function returnsTrue
only if every single pair satisfies this condition - Store this result in the variable
asc
- Use
-
Check for Monotone Decreasing Pattern:
- Similarly, use
pairwise(nums)
to generate consecutive pairs - For each pair
(a, b)
, check ifa >= b
- The
all()
function returnsTrue
only if every single pair satisfies this condition - Store this result in the variable
desc
- Similarly, use
-
Determine Final Result:
- Return
asc or desc
- This returns
True
if the array is either monotone increasing OR monotone decreasing - Returns
False
if neither condition is satisfied (meaning the array has both increasing and decreasing segments)
- Return
Key Implementation Details:
pairwise(nums)
creates an iterator of consecutive pairs: ifnums = [1, 2, 3]
, it yields(1, 2)
and(2, 3)
- The generator expressions
(a <= b for a, b in pairwise(nums))
and(a >= b for a, b in pairwise(nums))
are memory-efficient as they don't create intermediate lists - The
all()
function short-circuits - it stops checking as soon as it encounters aFalse
value, making it efficient
Time Complexity: O(n)
where n
is the length of the array, as we traverse the array at most twice (once for each check)
Space Complexity: O(1)
as we only use constant extra space for the boolean variables
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 solution with the array [3, 5, 5, 2]
:
Step 1: Check for Monotone Increasing
- Generate consecutive pairs using
pairwise([3, 5, 5, 2])
:- Pair 1:
(3, 5)
→ Check3 <= 5
→True
- Pair 2:
(5, 5)
→ Check5 <= 5
→True
- Pair 3:
(5, 2)
→ Check5 <= 2
→False
- Pair 1:
- Since not all pairs satisfy
a <= b
,asc = False
Step 2: Check for Monotone Decreasing
- Generate consecutive pairs using
pairwise([3, 5, 5, 2])
:- Pair 1:
(3, 5)
→ Check3 >= 5
→False
- The
all()
function short-circuits here and returnsFalse
- Pair 1:
- Since the first pair already fails,
desc = False
Step 3: Determine Final Result
- Return
asc or desc
→False or False
→False
- The array is not monotonic because it goes up from 3 to 5, then down from 5 to 2
Now let's trace through a monotonic example with [4, 3, 3, 1]
:
Step 1: Check for Monotone Increasing
- Generate consecutive pairs:
- Pair 1:
(4, 3)
→ Check4 <= 3
→False
- The
all()
function short-circuits and returnsFalse
- Pair 1:
- Result:
asc = False
Step 2: Check for Monotone Decreasing
- Generate consecutive pairs:
- Pair 1:
(4, 3)
→ Check4 >= 3
→True
- Pair 2:
(3, 3)
→ Check3 >= 3
→True
- Pair 3:
(3, 1)
→ Check3 >= 1
→True
- Pair 1:
- All pairs satisfy
a >= b
, sodesc = True
Step 3: Determine Final Result
- Return
asc or desc
→False or True
→True
- The array is monotonic (specifically, monotone decreasing)
Solution Implementation
1class Solution:
2 def isMonotonic(self, nums: List[int]) -> bool:
3 """
4 Check if an array is monotonic (either non-increasing or non-decreasing).
5
6 Args:
7 nums: List of integers to check for monotonicity
8
9 Returns:
10 True if the array is monotonic, False otherwise
11 """
12 # Import pairwise from itertools (Python 3.10+)
13 from itertools import pairwise
14
15 # Check if array is non-decreasing (ascending or equal)
16 is_ascending = all(a <= b for a, b in pairwise(nums))
17
18 # Check if array is non-increasing (descending or equal)
19 is_descending = all(a >= b for a, b in pairwise(nums))
20
21 # Array is monotonic if it's either non-decreasing or non-increasing
22 return is_ascending or is_descending
23
1class Solution {
2 /**
3 * Determines if an array is monotonic (either entirely non-increasing or non-decreasing).
4 *
5 * @param nums The input integer array to check
6 * @return true if the array is monotonic, false otherwise
7 */
8 public boolean isMonotonic(int[] nums) {
9 // Track if we've seen an ascending pattern (previous < current)
10 boolean hasAscendingPattern = false;
11
12 // Track if we've seen a descending pattern (previous > current)
13 boolean hasDescendingPattern = false;
14
15 // Iterate through the array comparing consecutive elements
16 for (int i = 1; i < nums.length; i++) {
17 // Check if current pair shows ascending pattern
18 if (nums[i - 1] < nums[i]) {
19 hasAscendingPattern = true;
20 }
21 // Check if current pair shows descending pattern
22 else if (nums[i - 1] > nums[i]) {
23 hasDescendingPattern = true;
24 }
25 // Note: Equal consecutive elements don't affect monotonicity
26
27 // If both patterns exist, array is not monotonic
28 if (hasAscendingPattern && hasDescendingPattern) {
29 return false;
30 }
31 }
32
33 // Array is monotonic if it has only one pattern or all elements are equal
34 return true;
35 }
36}
37
1class Solution {
2public:
3 bool isMonotonic(vector<int>& nums) {
4 // Track if the array has increasing and decreasing patterns
5 bool hasIncreasing = false;
6 bool hasDecreasing = false;
7
8 // Iterate through adjacent pairs of elements
9 for (int i = 1; i < nums.size(); ++i) {
10 // Check if current pair is in ascending order
11 if (nums[i - 1] < nums[i]) {
12 hasIncreasing = true;
13 }
14 // Check if current pair is in descending order
15 else if (nums[i - 1] > nums[i]) {
16 hasDecreasing = true;
17 }
18
19 // If both increasing and decreasing patterns exist, array is not monotonic
20 if (hasIncreasing && hasDecreasing) {
21 return false;
22 }
23 }
24
25 // Array is monotonic if it only has one direction (or all elements are equal)
26 return true;
27 }
28};
29
1/**
2 * Determines if an array is monotonic (either non-increasing or non-decreasing)
3 * @param nums - The input array of numbers to check
4 * @returns true if the array is monotonic, false otherwise
5 */
6function isMonotonic(nums: number[]): boolean {
7 // Track whether we've seen an ascending and descending pair
8 let hasAscending: boolean = false;
9 let hasDescending: boolean = false;
10
11 // Iterate through the array comparing adjacent elements
12 for (let i: number = 1; i < nums.length; i++) {
13 // Check if current pair is in ascending order
14 if (nums[i - 1] < nums[i]) {
15 hasAscending = true;
16 }
17 // Check if current pair is in descending order
18 else if (nums[i - 1] > nums[i]) {
19 hasDescending = true;
20 }
21
22 // If we've seen both ascending and descending pairs, array is not monotonic
23 if (hasAscending && hasDescending) {
24 return false;
25 }
26 }
27
28 // Array is monotonic if it's all ascending, all descending, or all equal
29 return true;
30}
31
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because:
- The
pairwise(nums)
function creates pairs of consecutive elements, yieldingn-1
pairs - The
all()
function with generator expression iterates through these pairs - In the worst case, both
asc
anddesc
checks need to examine alln-1
pairs - Even though we have two
all()
calls, they execute sequentially, giving usO(n) + O(n) = O(n)
The space complexity is O(1)
. This is because:
- The
pairwise()
function returns an iterator, not a list, so it doesn't create additional storage proportional to the input - The generator expressions
(a <= b for a, b in pairwise(nums))
and(a >= b for a, b in pairwise(nums))
are evaluated lazily and don't store all comparisons in memory - Only constant extra space is used for the boolean variables
asc
anddesc
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Python Version Compatibility Issue
The pairwise
function is only available in Python 3.10+. If you're using an older version of Python or submitting to a platform that doesn't support Python 3.10+, the code will fail with an ImportError
.
Solution: Implement your own pairwise logic or use alternative approaches:
class Solution:
def isMonotonic(self, nums: List[int]) -> bool:
# Manual pairwise implementation for compatibility
is_ascending = all(nums[i] <= nums[i+1] for i in range(len(nums)-1))
is_descending = all(nums[i] >= nums[i+1] for i in range(len(nums)-1))
return is_ascending or is_descending
2. Inefficient Double Traversal
The current solution potentially traverses the array twice - once to check for ascending and once for descending. In the worst case where the array is monotonic, both checks run completely.
Optimized Solution: Use a single pass to determine monotonicity by tracking both conditions simultaneously:
class Solution:
def isMonotonic(self, nums: List[int]) -> bool:
increasing = decreasing = True
for i in range(len(nums) - 1):
if nums[i] > nums[i + 1]:
increasing = False
if nums[i] < nums[i + 1]:
decreasing = False
return increasing or decreasing
3. Edge Case: Single Element or Empty Arrays
While the current solution handles these correctly (returns True
for arrays with 0 or 1 element), it's not immediately obvious why this works.
Clarification:
pairwise([])
produces no pairs, soall()
returnsTrue
(vacuous truth)pairwise([5])
produces no pairs, soall()
returnsTrue
- Both checks return
True
, making the array monotonic
Consider adding an explicit check for clarity:
class Solution:
def isMonotonic(self, nums: List[int]) -> bool:
if len(nums) <= 1:
return True
from itertools import pairwise
is_ascending = all(a <= b for a, b in pairwise(nums))
is_descending = all(a >= b for a, b in pairwise(nums))
return is_ascending or is_descending
4. Misunderstanding Monotonic Definition
A common misconception is thinking that equal consecutive elements break monotonicity. Remember that:
[1, 2, 2, 3]
is monotonic (non-decreasing)[3, 2, 2, 1]
is monotonic (non-increasing)- The conditions use
<=
and>=
, not<
and>
5. Early Termination Opportunity Missed
When checking both conditions separately, we might miss the opportunity to terminate early when we detect the array is neither increasing nor decreasing.
Early Termination Solution:
class Solution:
def isMonotonic(self, nums: List[int]) -> bool:
if len(nums) <= 2:
return True
# Determine initial direction from first different pair
direction = 0 # 0: unknown, 1: increasing, -1: decreasing
for i in range(len(nums) - 1):
if nums[i] < nums[i + 1]:
if direction == -1: # Was decreasing, now increasing
return False
direction = 1
elif nums[i] > nums[i + 1]:
if direction == 1: # Was increasing, now decreasing
return False
direction = -1
return True
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
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!