title: Longest Turbulent Subarray
slug: longest-turbulent-subarray
difficulty: medium
leetcode_id: 978
leetcode_url: https://leetcode.com/problems/longest-turbulent-subarray/
categories:
  - arrays
  - dynamic-programming
patterns:
  - sliding-window
  - dynamic-programming

function_signature: "def max_turbulence_size(arr: list[int]) -> int:"

test_cases:
  visible:
    - input: { arr: [9, 4, 2, 10, 7, 8, 8, 1, 9] }
      expected: 5
    - input: { arr: [4, 8, 12, 16] }
      expected: 2
    - input: { arr: [100] }
      expected: 1
  hidden:
    - input: { arr: [1, 2] }
      expected: 2
    - input: { arr: [2, 1] }
      expected: 2
    - input: { arr: [5, 5, 5, 5] }
      expected: 1
    - input: { arr: [1, 3, 2, 4, 3, 5, 4] }
      expected: 7
    - input: { arr: [9, 9] }
      expected: 1
    - input: { arr: [1, 2, 1, 2, 1, 2, 1] }
      expected: 7
    - input: { arr: [10, 5, 10, 5, 10] }
      expected: 5

description: |
  Given an integer array `arr`, return *the length of a maximum size turbulent subarray of* `arr`.

  A subarray is **turbulent** if the comparison sign flips between each adjacent pair of elements in the subarray.

  More formally, a subarray `[arr[i], arr[i + 1], ..., arr[j]]` of `arr` is said to be turbulent if and only if:

  - For `i <= k < j`:
    - `arr[k] > arr[k + 1]` when `k` is odd, and
    - `arr[k] < arr[k + 1]` when `k` is even.
  - Or, for `i <= k < j`:
    - `arr[k] > arr[k + 1]` when `k` is even, and
    - `arr[k] < arr[k + 1]` when `k` is odd.

constraints: |
  - `1 <= arr.length <= 4 * 10^4`
  - `0 <= arr[i] <= 10^9`

examples:
  - input: "arr = [9,4,2,10,7,8,8,1,9]"
    output: "5"
    explanation: "arr[1] > arr[2] < arr[3] > arr[4] < arr[5], which gives the turbulent subarray [4,2,10,7,8] with length 5."
  - input: "arr = [4,8,12,16]"
    output: "2"
    explanation: "All elements are strictly increasing, so the longest turbulent subarray is any pair of adjacent elements."
  - input: "arr = [100]"
    output: "1"
    explanation: "A single element is trivially turbulent."

explanation:
  intuition: |
    Imagine a stock price chart that zigzags up and down. A **turbulent subarray** is like finding the longest stretch where the chart alternates direction at every point — up, then down, then up, then down (or vice versa).

    The key insight is that we don't care about the absolute values or even the parity of indices. What matters is whether the **comparison sign flips** between consecutive pairs. If we have `a < b`, the next comparison must be `b > c` for the sequence to remain turbulent. If we ever see `a < b < c` (same direction twice) or `a == b` (no direction), the turbulent sequence breaks.

    Think of it like walking on a wavy path: every step must change direction. The moment you take two steps in the same direction (or stand still), you've left the turbulent zone.

    This naturally leads to a **sliding window** or **dynamic programming** approach: extend the current turbulent sequence while the alternation holds, and reset when it breaks.

  approach: |
    We solve this using a **Single Pass with Two Counters** approach:

    **Step 1: Handle the edge case**

    - If the array has only one element, return `1` immediately (a single element is trivially turbulent)

    &nbsp;

    **Step 2: Initialise tracking variables**

    - `inc`: Length of the longest turbulent subarray ending at the current position where the last comparison was increasing (`arr[i-1] < arr[i]`)
    - `dec`: Length of the longest turbulent subarray ending at the current position where the last comparison was decreasing (`arr[i-1] > arr[i]`)
    - Both start at `1` since a single element has length 1
    - `result`: Tracks the maximum length seen, initialised to `1`

    &nbsp;

    **Step 3: Iterate through the array starting from index 1**

    - For each position `i`, compare `arr[i]` with `arr[i-1]`:
      - If `arr[i-1] < arr[i]` (increasing): Set `inc = dec + 1` (extend the previous decreasing sequence) and reset `dec = 1`
      - If `arr[i-1] > arr[i]` (decreasing): Set `dec = inc + 1` (extend the previous increasing sequence) and reset `inc = 1`
      - If `arr[i-1] == arr[i]` (equal): Reset both `inc = 1` and `dec = 1` (turbulence broken)
    - Update `result` with `max(result, inc, dec)`

    &nbsp;

    **Step 4: Return the result**

    - Return `result` after processing all elements

    &nbsp;

    The key insight is that `inc` and `dec` track complementary states: to extend an increasing comparison, we need the previous comparison to have been decreasing (and vice versa). This is why we set `inc = dec + 1` when we see an increase.

  common_pitfalls:
    - title: Misunderstanding the Turbulence Definition
      description: |
        A common mistake is thinking turbulence requires a specific starting direction or depends on index parity in absolute terms. The definition is actually simpler: **comparisons must alternate**.

        The two cases in the problem description (odd/even rules) just describe the two possible patterns:
        - `< > < > ...` (starts with increase)
        - `> < > < ...` (starts with decrease)

        Both are valid turbulent sequences. Focus on whether consecutive comparisons flip, not on the index values.
      wrong_approach: "Checking if index is odd/even to determine expected comparison"
      correct_approach: "Track whether the last comparison was increasing or decreasing, and check if the current one flips"

    - title: Forgetting Equal Elements Break Turbulence
      description: |
        When `arr[i-1] == arr[i]`, there's no comparison sign — it's neither increasing nor decreasing. This breaks any turbulent sequence.

        For example, in `[9,4,2,10,7,8,8,1,9]`, the sequence `8,8` breaks the turbulence, so we can't connect `[4,2,10,7,8]` with `[8,1,9]`.

        Always reset both counters when encountering equal adjacent elements.
      wrong_approach: "Ignoring equal elements or treating them as continuing the pattern"
      correct_approach: "Reset both inc and dec to 1 when arr[i-1] == arr[i]"

    - title: Off-by-One in Sequence Length
      description: |
        A turbulent subarray with `k` elements has `k-1` comparisons. When extending, we add `1` to the previous counter (e.g., `inc = dec + 1`), not to the number of comparisons.

        For `[4, 2, 10]`: After seeing `4 > 2`, `dec = 2` (two elements). After seeing `2 < 10`, `inc = dec + 1 = 3` (three elements). This correctly counts the elements, not comparisons.
      wrong_approach: "Counting comparisons instead of elements, or incorrect initialization"
      correct_approach: "Initialize counters to 1 (single element) and add 1 when extending"

  key_takeaways:
    - "**Dual-state DP**: When a sequence's validity depends on its ending condition, track multiple states (here, `inc` and `dec`) that feed into each other"
    - "**Sliding window without explicit pointers**: The counters implicitly maintain a window — resetting to `1` is equivalent to starting a new window"
    - "**Alternation patterns**: For problems requiring alternating conditions, track what the *last* state was and check if the *current* state differs"
    - "**Pattern recognition**: This is similar to 'Wiggle Subsequence' (LeetCode 376), which asks for the longest *subsequence* (not subarray) with alternating differences"

  time_complexity: "O(n). We traverse the array exactly once, performing constant-time operations at each step."
  space_complexity: "O(1). We only use a fixed number of variables (`inc`, `dec`, `result`) regardless of input size."

solutions:
  - approach_name: Single Pass with Two Counters
    is_optimal: true
    code: |
      def max_turbulence_size(arr: list[int]) -> int:
          n = len(arr)
          if n == 1:
              return 1

          # inc: length of turbulent subarray ending here with last comparison increasing
          # dec: length of turbulent subarray ending here with last comparison decreasing
          inc = dec = 1
          result = 1

          for i in range(1, n):
              if arr[i - 1] < arr[i]:
                  # Current is increasing, extend from previous decreasing
                  inc = dec + 1
                  dec = 1  # Reset decreasing counter
              elif arr[i - 1] > arr[i]:
                  # Current is decreasing, extend from previous increasing
                  dec = inc + 1
                  inc = 1  # Reset increasing counter
              else:
                  # Equal elements break turbulence
                  inc = dec = 1

              result = max(result, inc, dec)

          return result
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array.

      **Space Complexity:** O(1) — Only three variables used.

      We maintain two counters that track the length of turbulent subarrays ending at the current position, distinguished by whether the last comparison was increasing or decreasing. When we see an increase, we can extend any previous sequence that ended with a decrease (and vice versa). Equal elements reset both counters since they break turbulence.

  - approach_name: Explicit Sliding Window
    is_optimal: true
    code: |
      def max_turbulence_size(arr: list[int]) -> int:
          n = len(arr)
          if n == 1:
              return 1

          # Helper to get comparison sign: -1, 0, or 1
          def cmp(a: int, b: int) -> int:
              if a < b:
                  return -1
              elif a > b:
                  return 1
              return 0

          result = 1
          left = 0  # Start of current turbulent window

          for right in range(1, n):
              c = cmp(arr[right - 1], arr[right])

              if c == 0:
                  # Equal elements: start fresh window after this position
                  left = right
              elif right == left + 1:
                  # Second element of window: any non-zero comparison is valid
                  result = max(result, 2)
              else:
                  # Check if comparison alternates from previous
                  prev_c = cmp(arr[right - 2], arr[right - 1])
                  if c == prev_c:
                      # Same direction twice: start new window from previous position
                      left = right - 1

              # Window size is right - left + 1
              result = max(result, right - left + 1)

          return result
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array.

      **Space Complexity:** O(1) — Only tracking window boundaries.

      This version explicitly maintains a sliding window with `left` and `right` pointers. The window shrinks (by moving `left`) when turbulence breaks: either due to equal elements or two consecutive comparisons in the same direction. This approach is conceptually clearer for those familiar with sliding window patterns.

  - approach_name: Dynamic Programming (Explicit)
    is_optimal: false
    code: |
      def max_turbulence_size(arr: list[int]) -> int:
          n = len(arr)
          if n == 1:
              return 1

          # dp_inc[i]: length of turbulent subarray ending at i with arr[i-1] < arr[i]
          # dp_dec[i]: length of turbulent subarray ending at i with arr[i-1] > arr[i]
          dp_inc = [1] * n
          dp_dec = [1] * n

          for i in range(1, n):
              if arr[i - 1] < arr[i]:
                  dp_inc[i] = dp_dec[i - 1] + 1
              elif arr[i - 1] > arr[i]:
                  dp_dec[i] = dp_inc[i - 1] + 1
              # If equal, both remain 1 (initialized value)

          return max(max(dp_inc), max(dp_dec))
    explanation: |
      **Time Complexity:** O(n) — Single pass to fill DP arrays.

      **Space Complexity:** O(n) — Two arrays of length n.

      This explicit DP formulation makes the recurrence clear: `dp_inc[i]` depends on `dp_dec[i-1]` and vice versa. While correct, it uses O(n) space unnecessarily — since we only need the previous values, we can reduce to O(1) space as shown in the optimal solution. Included here to illustrate the DP structure before optimization.
