LeetCode 978 - Longest Turbulent Subarray
The problem asks us to find the length of the longest turbulent subarray inside a given integer array arr. A subarray is considered turbulent if the relationship between every adjacent pair of numbers alternates between greater than () and less than (<).
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Sliding Window
Solution
Problem Understanding
The problem asks us to find the length of the longest turbulent subarray inside a given integer array arr.
A subarray is considered turbulent if the relationship between every adjacent pair of numbers alternates between greater than (>) and less than (<). In other words, the comparison sign must continuously flip as we move through the subarray.
For example, consider:
[4, 2, 10, 7, 8]
The comparisons are:
4 > 2 < 10 > 7 < 8
Since the comparison alternates between > and < at every step, this subarray is turbulent.
The problem does not care which pattern starts first. Both of the following are valid:
a > b < c > d
and
a < b > c < d
The input is an integer array arr, where:
arr[i]represents the value at indexi- The array length can be as large as
4 * 10^4 - Each value can be as large as
10^9
The output should be the maximum length of any contiguous turbulent subarray.
The constraints are important because they immediately rule out inefficient solutions. Since n can be up to 40,000, an O(n²) solution may be too slow in practice, and anything worse is definitely infeasible. This strongly suggests that we should aim for a linear or near-linear approach.
There are several important edge cases to think about upfront.
If the array contains only one element, the answer must be 1 because a single element is trivially turbulent.
If adjacent elements are equal, turbulence immediately breaks because neither > nor < holds. For example:
[9, 9]
This has a longest turbulent subarray of length 1.
Arrays that are strictly increasing or strictly decreasing are also interesting:
[1, 2, 3, 4]
The comparisons never alternate, so the best answer is 2, because any adjacent pair is technically turbulent.
We also need to handle cases where turbulence breaks and restarts multiple times.
Approaches
Brute Force Approach
A straightforward solution is to examine every possible subarray and check whether it is turbulent.
For every starting index i, we try every ending index j, then verify whether the comparisons alternate correctly between every adjacent pair.
To validate a subarray, we compare consecutive elements and ensure that:
- No adjacent values are equal
- The comparison direction alternates
If any comparison fails, the subarray is not turbulent.
This method is correct because it exhaustively checks all possibilities and guarantees that the maximum valid length will eventually be found.
However, it is computationally expensive. There are O(n²) subarrays, and checking each subarray can take O(n) time, resulting in O(n³) complexity. Even with some optimization, it remains too slow for n = 40,000.
Key Insight for an Optimal Solution
The key observation is that turbulence depends only on the relationship between adjacent elements.
Instead of repeatedly checking entire subarrays, we can process the array in one pass and track the current turbulent window.
At every index, we compare:
arr[i - 1] and arr[i]
There are three possibilities:
arr[i] > arr[i - 1]arr[i] < arr[i - 1]arr[i] == arr[i - 1]
If the comparison alternates compared to the previous comparison, the turbulent sequence continues.
If the comparison becomes equal, turbulence resets completely.
If the comparison repeats the same direction, the current turbulent sequence breaks, but we can restart from the previous element.
This naturally leads to a sliding window / dynamic programming style solution that runs in linear time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Checks every subarray and validates turbulence |
| Optimal | O(n) | O(1) | Tracks alternating comparisons in one pass |
Algorithm Walkthrough
Optimal Sliding Window Approach
We maintain:
current_length, the length of the current turbulent subarraymax_length, the best answer found so farprevious_comparison, which stores whether the previous pair was increasing, decreasing, or equal
We represent comparisons as:
1 -> increasing
-1 -> decreasing
0 -> equal
Step 1: Initialize Variables
Start with:
max_length = 1
current_length = 1
previous_comparison = 0
A single element is always turbulent, so the minimum answer is 1.
Step 2: Iterate Through Adjacent Pairs
For each index i from 1 to n - 1, compare:
arr[i] with arr[i - 1]
Determine the current comparison:
1if increasing-1if decreasing0if equal
Step 3: Handle Equal Elements
If:
arr[i] == arr[i - 1]
Then turbulence breaks immediately.
Reset:
current_length = 1
because the current element starts a new possible subarray.
Step 4: Extend a Turbulent Sequence
If the current comparison alternates with the previous one:
current_comparison != previous_comparison
and neither is 0, then the turbulent pattern continues.
Increase:
current_length += 1
Step 5: Restart the Sequence
If the comparison direction repeats:
>, >
or
<, <
then turbulence breaks.
However, the current pair itself forms a valid turbulent subarray of length 2.
So reset:
current_length = 2
Step 6: Update the Maximum
After processing each element:
max_length = max(max_length, current_length)
Store the current comparison for the next iteration.
Why it works
The algorithm maintains the invariant that current_length always represents the length of the longest turbulent subarray ending at the current index.
Whenever adjacent comparisons alternate, we safely extend the current subarray. When the pattern breaks, we restart at the smallest valid turbulent segment. Since every index is processed exactly once and every possible turbulent ending is considered, the maximum length found is guaranteed to be correct.
Python Solution
from typing import List
class Solution:
def maxTurbulenceSize(self, arr: List[int]) -> int:
n = len(arr)
if n == 1:
return 1
max_length = 1
current_length = 1
previous_comparison = 0
for i in range(1, n):
if arr[i] > arr[i - 1]:
current_comparison = 1
elif arr[i] < arr[i - 1]:
current_comparison = -1
else:
current_comparison = 0
if current_comparison == 0:
current_length = 1
elif current_comparison == -previous_comparison:
current_length += 1
else:
current_length = 2
max_length = max(max_length, current_length)
previous_comparison = current_comparison
return max_length
The implementation begins by handling the smallest edge case, an array of length one.
We then maintain three variables:
max_lengthstores the best answer.current_lengthtracks the turbulent subarray ending at the current index.previous_comparisonremembers the direction of the previous comparison.
At each iteration, we determine whether the current pair is increasing, decreasing, or equal.
If the elements are equal, turbulence resets because equal numbers cannot satisfy either alternating condition.
If the current comparison is the opposite of the previous one, we extend the turbulent segment.
Otherwise, turbulence breaks, but the current adjacent pair still forms a valid turbulent subarray of length 2.
Finally, we update the global maximum after each step.
Go Solution
func maxTurbulenceSize(arr []int) int {
n := len(arr)
if n == 1 {
return 1
}
maxLength := 1
currentLength := 1
previousComparison := 0
for i := 1; i < n; i++ {
currentComparison := 0
if arr[i] > arr[i-1] {
currentComparison = 1
} else if arr[i] < arr[i-1] {
currentComparison = -1
}
if currentComparison == 0 {
currentLength = 1
} else if currentComparison == -previousComparison {
currentLength++
} else {
currentLength = 2
}
if currentLength > maxLength {
maxLength = currentLength
}
previousComparison = currentComparison
}
return maxLength
}
The Go implementation mirrors the Python solution closely.
Go slices are already reference types, so no special array handling is needed. Since the constraints fit comfortably inside Go's int, integer overflow is not a concern here. We explicitly initialize currentComparison to 0, which naturally represents equal elements.
Unlike Python, Go does not provide a built in max() function for integers, so we update maxLength using a simple conditional comparison.
Worked Examples
Example 1
Input:
arr = [9,4,2,10,7,8,8,1,9]
| i | Pair | Comparison | Previous | Current Length | Max Length |
|---|---|---|---|---|---|
| 1 | 9,4 | -1 | 0 | 2 | 2 |
| 2 | 4,2 | -1 | -1 | 2 | 2 |
| 3 | 2,10 | 1 | -1 | 3 | 3 |
| 4 | 10,7 | -1 | 1 | 4 | 4 |
| 5 | 7,8 | 1 | -1 | 5 | 5 |
| 6 | 8,8 | 0 | 1 | 1 | 5 |
| 7 | 8,1 | -1 | 0 | 2 | 5 |
| 8 | 1,9 | 1 | -1 | 3 | 5 |
Final answer:
5
The longest turbulent segment is:
[4,2,10,7,8]
Example 2
Input:
arr = [4,8,12,16]
| i | Pair | Comparison | Previous | Current Length | Max Length |
|---|---|---|---|---|---|
| 1 | 4,8 | 1 | 0 | 2 | 2 |
| 2 | 8,12 | 1 | 1 | 2 | 2 |
| 3 | 12,16 | 1 | 1 | 2 | 2 |
Final answer:
2
No alternating pattern exists.
Example 3
Input:
arr = [100]
Since there is only one element:
answer = 1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We scan the array once |
| Space | O(1) | Only a few variables are used |
The algorithm processes each adjacent pair exactly once. No nested loops are used, and no auxiliary data structures grow with input size. This gives linear runtime and constant extra space.
Test Cases
solution = Solution()
assert solution.maxTurbulenceSize([9,4,2,10,7,8,8,1,9]) == 5 # example case
assert solution.maxTurbulenceSize([4,8,12,16]) == 2 # monotonic increasing
assert solution.maxTurbulenceSize([100]) == 1 # single element
assert solution.maxTurbulenceSize([9,9]) == 1 # equal adjacent elements
assert solution.maxTurbulenceSize([1,2]) == 2 # smallest turbulent pair
assert solution.maxTurbulenceSize([2,1]) == 2 # decreasing pair
assert solution.maxTurbulenceSize([1,2,1,2,1]) == 5 # fully turbulent
assert solution.maxTurbulenceSize([1,2,3,4,5]) == 2 # strictly increasing
assert solution.maxTurbulenceSize([5,4,3,2,1]) == 2 # strictly decreasing
assert solution.maxTurbulenceSize([1,1,1,1]) == 1 # all equal
assert solution.maxTurbulenceSize([9,4,9]) == 3 # short turbulence
assert solution.maxTurbulenceSize([4,8,4,8,4,8]) == 6 # long alternating
assert solution.maxTurbulenceSize([1,3,2,2,4,5]) == 3 # reset after equality
assert solution.maxTurbulenceSize([100,50,100,50,100]) == 5 # repeated alternation
| Test | Why |
|---|---|
[9,4,2,10,7,8,8,1,9] |
Validates the main example |
[4,8,12,16] |
Monotonic increasing sequence |
[100] |
Smallest valid input |
[9,9] |
Equal values break turbulence |
[1,2] |
Minimal increasing pair |
[2,1] |
Minimal decreasing pair |
[1,2,1,2,1] |
Fully alternating sequence |
[1,2,3,4,5] |
No alternation |
[5,4,3,2,1] |
Strictly decreasing |
[1,1,1,1] |
Entire array equal |
[9,4,9] |
Small valid turbulence |
[4,8,4,8,4,8] |
Longest possible turbulence |
[1,3,2,2,4,5] |
Equality resets sequence |
[100,50,100,50,100] |
Repeated alternating comparisons |
Edge Cases
Single Element Array
An array with only one element is trivially turbulent because there are no adjacent comparisons to violate the rule.
This case can easily cause bugs if the implementation assumes at least one comparison exists. Our implementation handles it immediately with:
if n == 1:
return 1
Equal Adjacent Elements
Equal values are especially important because they completely break turbulence.
For example:
[4, 4, 5]
The pair 4 == 4 cannot participate in any alternating comparison pattern. Our implementation resets current_length to 1, ensuring the algorithm correctly starts fresh afterward.
Monotonic Arrays
Arrays that are entirely increasing or entirely decreasing can be deceptive.
For example:
[1,2,3,4]
A naive implementation might incorrectly return 1, but any adjacent pair of elements already forms a valid turbulent subarray of length 2.
Our implementation correctly resets to 2 whenever the comparison direction repeats, ensuring the minimum valid adjacent turbulent pair is preserved.