Facebook Pixel

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:

  1. Monotone Increasing: Every element is less than or equal to the next element. In other words, for any two indices i and j where i <= j, we have nums[i] <= nums[j].

  2. Monotone Decreasing: Every element is greater than or equal to the next element. In other words, for any two indices i and j where i <= j, we have nums[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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 satisfy a <= b
  • For decreasing: every pair (a, b) should satisfy a >= 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:

  1. Check if all consecutive pairs are non-decreasing (a <= b for every pair)
  2. Check if all consecutive pairs are non-increasing (a >= b for every pair)
  3. 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:

  1. Check for Monotone Increasing Pattern:

    • Use pairwise(nums) to generate consecutive pairs from the array
    • For each pair (a, b), check if a <= b
    • The all() function returns True only if every single pair satisfies this condition
    • Store this result in the variable asc
  2. Check for Monotone Decreasing Pattern:

    • Similarly, use pairwise(nums) to generate consecutive pairs
    • For each pair (a, b), check if a >= b
    • The all() function returns True only if every single pair satisfies this condition
    • Store this result in the variable desc
  3. 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)

Key Implementation Details:

  • pairwise(nums) creates an iterator of consecutive pairs: if nums = [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 a False 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 Evaluator

Example 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) → Check 3 <= 5True
    • Pair 2: (5, 5) → Check 5 <= 5True
    • Pair 3: (5, 2) → Check 5 <= 2False
  • 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) → Check 3 >= 5False
    • The all() function short-circuits here and returns False
  • Since the first pair already fails, desc = False

Step 3: Determine Final Result

  • Return asc or descFalse or FalseFalse
  • 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) → Check 4 <= 3False
    • The all() function short-circuits and returns False
  • Result: asc = False

Step 2: Check for Monotone Decreasing

  • Generate consecutive pairs:
    • Pair 1: (4, 3) → Check 4 >= 3True
    • Pair 2: (3, 3) → Check 3 >= 3True
    • Pair 3: (3, 1) → Check 3 >= 1True
  • All pairs satisfy a >= b, so desc = True

Step 3: Determine Final Result

  • Return asc or descFalse or TrueTrue
  • 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, yielding n-1 pairs
  • The all() function with generator expression iterates through these pairs
  • In the worst case, both asc and desc checks need to examine all n-1 pairs
  • Even though we have two all() calls, they execute sequentially, giving us O(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 and desc

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, so all() returns True (vacuous truth)
  • pairwise([5]) produces no pairs, so all() returns True
  • 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More