Longest Alternating Sequences — Problem Statement & Solution Guide
Quick Answer & Algorithm Key Takeaway
Master coding challenges related to Arrays and solve the Longest Alternating Sequences problem optimally.
O(n)O(1)Problem Description
Given an integer array values, find two numbers. The first is the maximum length of a contiguous subsequence that first strictly increases and then strictly decreases (an “up‑down” segment). The second is the maximum length of a contiguous subsequence that first strictly decreases and then strictly increases (a “down‑up” segment). A segment may consist of only the increasing part or only the decreasing part, but the direction change, if present, must be strict. Return the two lengths as two space‑separated integers.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Longest Alternating Sequences"
WHY DOES IT MATTER?
Detecting up‑down or down‑up patterns is a core skill for recognizing piecewise monotonic behavior, which appears in signal processing, stock trend analysis, and performance profiling. Mastery of this pattern demonstrates the ability to convert a global optimization problem into local state tracking.
OPTIMIZATION CHALLENGE
The breakthrough is realizing that each element’s contribution to a candidate segment can be pre‑computed in two linear passes—forward for increasing lengths and backward for decreasing lengths—so the peak evaluation becomes O(1) per index instead of re‑scanning the whole subarray.
REAL-WORLD CONNECTION
Think of a server’s CPU load that ramps up during peak traffic and then cools down after the burst. Identifying the longest such ramp‑down cycle helps capacity planners allocate resources efficiently, analogous to finding the longest mountain in an array.
During an interview, compute the forward inc[] array on the fly while scanning, then immediately compute the backward dec[] array in a second pass; avoid storing both arrays if you can merge the second pass with the final max‑check to keep space O(1).
COMPLEXITY AT A GLANCE
O(n)O(1)Core Theory — Why This Approach?
The problem is a variant of the classic "longest mountain" challenge. A mountain is a contiguous subarray that strictly increases to a peak and then strictly decreases. To solve it efficiently we need to know, for each index, the length of the increasing run ending at that index and the length of the decreasing run starting at that index. Naïve enumeration of every possible subarray would be O(n²) and quickly exceeds time limits for large n because each candidate segment would be re‑scanned many times. The optimal paradigm leverages linear scans: one forward pass computes the length of the current increasing streak, and one backward pass computes the length of the current decreasing streak. By combining these two arrays we can evaluate every potential peak in O(1) time, yielding an overall O(n) solution. This approach also naturally yields the symmetric "down‑up" segment by swapping the direction of the monotonic checks, so both answers are obtained in a single pass pair.
Interview Questions on This Problem
Q1How would you modify the longest mountain algorithm to also return the start and end indices of the optimal up‑down segment?
Maintain the index where the current increasing streak began; when a decreasing streak ends, compute the total length and compare with the best. If it improves the best, store the start index (the beginning of the increasing streak) and the current index as the end.
Q2In a streaming context where the array is too large to fit in memory, can you compute the longest up‑down segment using O(1) extra space?
Yes. Use a two‑pointer sliding window that expands while the sequence is strictly increasing, then switches to decreasing. When monotonicity breaks, reset the window to start at the last peak. Track the maximum length seen. This works because only local monotonic information is needed.
Q3Why does the classic O(n) mountain solution fail if the array contains equal adjacent values, and how do you handle it?
The definition requires strict monotonicity; equal values break both increasing and decreasing runs, causing false peaks. The algorithm must reset the current streak counters whenever values are equal, treating them as a boundary between potential segments.
Examples
Input
[1,3,5,4,2]
Output
5 2
Explanation: The subarray [1,3,5,4,2] strictly rises from 1 to 5 and then falls to 2, giving an up‑down length of 5. No longer down‑up pattern exists; the best we can do is any two adjacent elements such as [5,4], so the down‑up length is 2.
Input
[9,7,5,6,8,10]
Output
2 6
Explanation: The longest up‑down segment is just the rise [5,6] or any two‑element rise, so its length is 2. The whole array [9,7,5,6,8,10] first falls from 9 to 5 and then rises to 10, giving a down‑up length of 6.
Input
[4,4,4]
Output
1 1
Explanation: All elements are equal, so no strict increase or decrease can be formed. The only valid segments are single‑element subarrays, each of length 1, for both patterns.
Constraints
- 1 <= values.length <= 100000
- -1000000000 <= values[i] <= 1000000000
- Solution must run in O(n) time and O(1) extra space
Optimal Approach & Strategy
Perform two linear passes to compute increasing and decreasing run lengths for each position, then combine them in a final linear scan to obtain the longest up‑down and down‑up segments—overall O(n) time.
Brute Force Approach
Check every possible subarray, verify if it first strictly increases then strictly decreases (or vice‑versa), and track the maximum length—this is O(n²).
Code Solutions
function longestAlternatingSequences(values) {
if (values.length === 0) return [0, 0];
const n = values.length;
const inc = new Array(n).fill(1);
const dec = new Array(n).fill(1);
for (let i = 1; i < n; ++i) {
if (values[i] > values[i - 1]) inc[i] = inc[i - 1] + 1;
if (values[i] < values[i - 1]) dec[i] = dec[i - 1] + 1;
}
let maxUpDown = 1, maxDownUp = 1;
for (let i = 0; i < n; ++i) {
const len = inc[i] + dec[i] - 1; // peak at i
if (len > maxUpDown) maxUpDown = len;
}
// compute increasing run from right for down‑up
const incR = new Array(n).fill(1);
for (let i = n - 2; i >= 0; --i) {
if (values[i] < values[i + 1]) incR[i] = incR[i + 1] + 1;
}
for (let i = 0; i < n; ++i) {
const len = dec[i] + incR[i] - 1; // valley at i
if (len > maxDownUp) maxDownUp = len;
}
return [maxUpDown, maxDownUp];
}
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
let lines = [];
readline.on('line', line => lines.push(line.trim()));
readline.on('close', () => {
const n = parseInt(lines[0] || '0', 10);
const values = (lines[1] || '').split(/\s+/).filter(s=>s.length).map(Number);
const [upDown, downUp] = longestAlternatingSequences(values);
console.log(upDown + ' ' + downUp);
});#include <bits/stdc++.h>
using namespace std;
pair<int,int> longestAlternatingSequences(const vector<int>& values) {
if(values.empty()) return {0,0};
int n = values.size();
// inc[i] = length of strictly increasing run ending at i
// dec[i] = length of strictly decreasing run ending at i
vector<int> inc(n,1), dec(n,1);
for(int i=1;i<n;++i){
if(values[i]>values[i-1]) inc[i]=inc[i-1]+1;
if(values[i]<values[i-1]) dec[i]=dec[i-1]+1;
}
int maxUpDown = 1, maxDownUp = 1;
// Up‑Down: increasing then decreasing. The peak is at position i.
for(int i=0;i<n;++i){
// length = inc[i] + dec[i] - 1 (peak counted twice)
int len = inc[i] + dec[i] - 1;
maxUpDown = max(maxUpDown, len);
}
// Down‑Up: decreasing then increasing. The valley is at position i.
// We can reuse inc and dec by swapping roles.
// Compute incFromLeft (already) and decFromLeft (already). For valley we need decreasing run ending at i and increasing run starting at i.
// Compute incFromRight.
vector<int> incR(n,1);
for(int i=n-2;i>=0;--i){
if(values[i]<values[i+1]) incR[i]=incR[i+1]+1;
}
for(int i=0;i<n;++i){
int len = dec[i] + incR[i] - 1;
maxDownUp = max(maxDownUp, len);
}
return {maxUpDown, maxDownUp};
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n; if(!(cin>>n)) return 0;
vector<int> a(n);
for(int i=0;i<n;++i) cin>>a[i];
auto ans = longestAlternatingSequences(a);
cout << ans.first << " " << ans.second << "\n";
return 0;
}import java.util.*;
public class Main {
// Returns [maxUpDown, maxDownUp]
public static int[] longestAlternatingSequences(int[] values) {
if (values.length == 0) return new int[]{0, 0};
int n = values.length;
int[] inc = new int[n]; // increasing run ending at i
int[] dec = new int[n]; // decreasing run ending at i
Arrays.fill(inc, 1);
Arrays.fill(dec, 1);
for (int i = 1; i < n; i++) {
if (values[i] > values[i - 1]) inc[i] = inc[i - 1] + 1;
if (values[i] < values[i - 1]) dec[i] = dec[i - 1] + 1;
}
int maxUpDown = 1;
for (int i = 0; i < n; i++) {
int len = inc[i] + dec[i] - 1; // peak at i
if (len > maxUpDown) maxUpDown = len;
}
// increasing run from right for down‑up
int[] incR = new int[n];
Arrays.fill(incR, 1);
for (int i = n - 2; i >= 0; i--) {
if (values[i] < values[i + 1]) incR[i] = incR[i + 1] + 1;
}
int maxDownUp = 1;
for (int i = 0; i < n; i++) {
int len = dec[i] + incR[i] - 1; // valley at i
if (len > maxDownUp) maxDownUp = len;
}
return new int[]{maxUpDown, maxDownUp};
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.hasNextInt() ? sc.nextInt() : 0;
int[] values = new int[n];
for (int i = 0; i < n; i++) values[i] = sc.nextInt();
int[] ans = longestAlternatingSequences(values);
System.out.println(ans[0] + " " + ans[1]);
}
}
def longestAlternatingSequences(values):
if not values:
return (0, 0)
n = len(values)
inc = [1] * n # increasing run ending at i
dec = [1] * n # decreasing run ending at i
for i in range(1, n):
if values[i] > values[i - 1]:
inc[i] = inc[i - 1] + 1
if values[i] < values[i - 1]:
dec[i] = dec[i - 1] + 1
max_up_down = 1
for i in range(n):
length = inc[i] + dec[i] - 1 # peak at i
if length > max_up_down:
max_up_down = length
# increasing run from the right for down‑up
inc_r = [1] * n
for i in range(n - 2, -1, -1):
if values[i] < values[i + 1]:
inc_r[i] = inc_r[i + 1] + 1
max_down_up = 1
for i in range(n):
length = dec[i] + inc_r[i] - 1 # valley at i
if length > max_down_up:
max_down_up = length
return (max_up_down, max_down_up)
if __name__ == "__main__":
import sys
data = sys.stdin.read().strip().split()
if not data:
print("0 0")
sys.exit()
n = int(data[0])
values = list(map(int, data[1:1 + n]))
up, down = longestAlternatingSequences(values)
print(up, down)function longestAlternatingSequences(values) {
if (values.length === 0) return [0, 0];
const n = values.length;
const inc = new Array(n).fill(1);
const dec = new Array(n).fill(1);
for (let i = 1; i < n; ++i) {
if (values[i] > values[i - 1]) inc[i] = inc[i - 1] + 1;
if (values[i] < values[i - 1]) dec[i] = dec[i - 1] + 1;
}
let maxUpDown = 1, maxDownUp = 1;
for (let i = 0; i < n; ++i) {
const len = inc[i] + dec[i] - 1; // peak at i
if (len > maxUpDown) maxUpDown = len;
}
// compute increasing run from right for down‑up
const incR = new Array(n).fill(1);
for (let i = n - 2; i >= 0; --i) {
if (values[i] < values[i + 1]) incR[i] = incR[i + 1] + 1;
}
for (let i = 0; i < n; ++i) {
const len = dec[i] + incR[i] - 1; // valley at i
if (len > maxDownUp) maxDownUp = len;
}
return [maxUpDown, maxDownUp];
}
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
let lines = [];
readline.on('line', line => lines.push(line.trim()));
readline.on('close', () => {
const n = parseInt(lines[0] || '0', 10);
const values = (lines[1] || '').split(/\s+/).filter(s=>s.length).map(Number);
const [upDown, downUp] = longestAlternatingSequences(values);
console.log(upDown + ' ' + downUp);
});Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.