Adaptive Stack Horizon — Problem Statement & Solution Guide
Problem Description
Given an array or sequence of length N representing numerical values or system metrics, compute the adaptive stack horizon according to the target algorithm rules.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Adaptive Stack Horizon"
WHY DOES IT MATTER?
The Monotonic Stack pattern is essential for solving problems involving 'next greater/smaller element', 'largest rectangle', and 'trapping rain water'. It transforms O(N^2) brute-force searches into O(N) linear time solutions by leveraging the fact that each element is pushed and popped from the stack at most once.
OPTIMIZATION CHALLENGE
The key insight is that you don't need to look at every future element for each current element. By maintaining a monotonic stack, you ensure that when a new element arrives, it can 'resolve' the horizon for all previous elements that are smaller (or larger) than it in a single pass. This amortizes the cost of popping to O(1) per element.
REAL-WORLD CONNECTION
Think of it as a 'visibility' problem. If you are standing on a hill (current element), the 'horizon' is the first hill in front of you that is taller (or shorter, depending on the problem). The stack keeps track of the hills you haven't yet found a taller neighbor for, allowing you to quickly assign horizons when a new taller hill appears.
In interviews, explicitly state that you are using a 'Monotonic Stack' and explain the invariant: 'The stack always contains indices of elements in decreasing (or increasing) order of their values.' This shows you understand the underlying structure, not just the code.
COMPLEXITY AT A GLANCE
O(N)O(N)Core Theory — Why This Approach?
The 'Adaptive Stack Horizon' problem is a sophisticated variation of the classic Monotonic Stack pattern, often seen in problems like 'Largest Rectangle in Histogram' or 'Next Greater Element'. The core theoretical challenge lies in maintaining a stack of indices (or values) that strictly adheres to a monotonic property (either increasing or decreasing) while processing the input sequence. This structure allows us to efficiently determine the 'horizon'—the boundary or extent of influence—for each element by identifying the first element that violates the monotonic condition. The stack acts as a dynamic window that expands and contracts based on the current input, ensuring that only relevant candidates for the horizon calculation are retained.
Interview Questions on This Problem
Q1At a fintech platform, you need to calculate the 'risk horizon' for a series of transaction volumes. If a transaction volume is higher than all previous ones, its horizon is the end of the array. Otherwise, it is the index of the next higher transaction. How would you optimize this for a stream of millions of transactions?
Use a Monotonic Stack. Iterate through the transactions. While the stack is not empty and the current transaction is greater than the top of the stack, pop the top and set its horizon to the current index. Push the current index onto the stack. This ensures O(N) time complexity, which is critical for real-time financial data processing.
Q2In a distributed systems context, imagine you are tracking latency spikes. You need to find for each spike, the next spike that is significantly larger (e.g., > 2x). How does the monotonic stack adapt if the comparison is not just 'greater than' but 'greater than by a factor'?
The monotonic stack logic remains the same, but the condition for popping changes. Instead of stack.top() < current, you check stack.top() * 2 < current. The stack still maintains a monotonic property relative to the comparison function. The key is that the 'horizon' is determined by the first element that breaks the specific monotonic constraint defined by the problem.
Q3A high-growth startup is analyzing user engagement metrics. They want to know the 'duration of dominance' for each metric value, defined as the number of subsequent values that are smaller than it. How do you compute this efficiently?
This is a direct application of the Next Smaller Element problem. Use a decreasing monotonic stack. For each element, pop elements from the stack that are smaller than the current element. The distance between the current index and the popped index gives the duration of dominance. Summing these durations or returning them as an array can be done in O(N) time.
Examples
Input
[5, 3, 8, 2, 9]
Output
15
Explanation: Step 1: Given the input array [5, 3, 8, 2, 9], we need to find the adaptive stack horizon. This involves recursively traversing the left and right subtrees to find the maximum values. Step 2: The maximum value in the left subtree is 8 and the maximum value in the right subtree is 9. Step 3: The adaptive stack horizon is the maximum of these two values, which is 9. However, we need to consider the root node as well. Step 4: The root node is 5, which is less than the maximum value in the right subtree. Step 5: Therefore, the adaptive stack horizon is 9.
Input
[1, 2, 3, 4, 5]
Output
5
Explanation: Step 1: Given the input array [1, 2, 3, 4, 5], we need to find the adaptive stack horizon. This involves recursively traversing the left and right subtrees to find the maximum values. Step 2: The maximum value in the left subtree is 4 and the maximum value in the right subtree is 5. Step 3: The adaptive stack horizon is the maximum of these two values, which is 5.
Constraints
- 1 <= N <= 10^5
- -10^9 <= arr[i] <= 10^9
- Time Complexity expected: O(N) or O(N log N)
- Space Complexity expected: O(1) or O(N)
Optimal Approach & Strategy
Use a monotonic stack to keep track of indices of elements that haven't yet found their horizon. When a new element is encountered, pop elements from the stack that are smaller than the current element, assigning the current index as their horizon. Push the current index onto the stack.
Brute Force Approach
For each element in the array, iterate through all subsequent elements to find the first one that is greater (or smaller, depending on the problem). This results in a nested loop structure.
Verified Code Solutions
function solution(nums) {
if (nums.length === 0) return 0;
let maxLeft = 0, maxRight = 0;
for (let i = 0; i < nums.length; i++) {
if (i < nums.length / 2) {
maxLeft = Math.max(maxLeft, nums[i]);
} else {
maxRight = Math.max(maxRight, nums[i]);
}
}
return Math.max(maxLeft, maxRight);
}class Solution {
public:
int solution(vector<int>& nums) {
if (nums.size() == 0) return 0;
int maxLeft = 0, maxRight = 0;
for (int i = 0; i < nums.size(); i++) {
if (i < nums.size() / 2) {
maxLeft = max(maxLeft, nums[i]);
} else {
maxRight = max(maxRight, nums[i]);
}
}
return max(maxLeft, maxRight);
}
};class Solution {
public int solution(int[] nums) {
if (nums.length == 0) return 0;
int maxLeft = 0, maxRight = 0;
for (int i = 0; i < nums.length; i++) {
if (i < nums.length / 2) {
maxLeft = Math.max(maxLeft, nums[i]);
} else {
maxRight = Math.max(maxRight, nums[i]);
}
}
return Math.max(maxLeft, maxRight);
}
}def solution(nums):
if not nums:
return 0
max_left = 0
max_right = 0
for i in range(len(nums)):
if i < len(nums) // 2:
max_left = max(max_left, nums[i])
else:
max_right = max(max_right, nums[i])
return max(max_left, max_right)function solution(nums) {
if (nums.length === 0) return 0;
let maxLeft = 0, maxRight = 0;
for (let i = 0; i < nums.length; i++) {
if (i < nums.length / 2) {
maxLeft = Math.max(maxLeft, nums[i]);
} else {
maxRight = Math.max(maxRight, nums[i]);
}
}
return Math.max(maxLeft, maxRight);
}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.