Replace Elements with Greatest Element on Right Side¶
Question¶
Given an array arr, replace every element in that array with the greatest element strictly to its right, and replace the last element with -1. Return the modified array in-place.
Solution¶
Pattern¶
Reverse Traversal (Right-to-Left) When a problem requires aggregating information from elements to the right of the current index, iterate from right to left. This carries the "future" state backward, allowing you to compute answers in \(O(1)\) time per element.
How to Identify¶
- The problem requires making decisions based on the "remaining" array (everything to the right).
- A brute force approach naturally leans toward nested loops \(O(N^2)\).
- The information needed from the right side is an aggregate metric (e.g., maximum, minimum, sum, product).
Description¶
Step-by-step explanation:
- Validate the input to ensure the array is not null or empty.
- Initialize a state variable,
maxRight, to-1. This mathematically satisfies the requirement for the last element and seeds our maximum tracker. - Iterate backward through the array, starting from
arr.length - 1down to0. - At each step, store the actual value of
arr[i]into a temporary variablecurrentVal. We need this because we are about to overwritearr[i], but we still need its original value to calculate the max for the elements to its left. - Overwrite
arr[i]withmaxRight. - Update
maxRightby taking the maximum of its current value andcurrentVal. - Continue until the loop terminates, then return the in-place modified array.
The Intuition¶
Think of this algorithm as walking backward through time. If you walk forward (left-to-right), you don't know what the biggest number ahead of you is unless you run ahead to check (costing \(O(N)\) time per step). But if you start at the end and walk backward, you can just carry the biggest number you've found so far in your pocket. When you step onto a new number, you simply take the big number out of your pocket, write it down, and then check if the number you just stepped on is bigger than the one in your pocket. If it is, you swap it. This gives you perfect "hindsight" in a single pass.
Complexity¶
| Label | Worst | Average |
|---|---|---|
| Time Complexity | \(O(N)\) | \(O(N)\) |
| Space Complexity | \(O(1)\) | \(O(1)\) |
Time Complexity¶
We traverse the array of size \(N\) exactly once. Inside the loop, we perform \(O(1)\) constant-time operations (variable assignments and a Math.max comparison). Total time complexity is strictly \(O(N)\).
Space Complexity¶
We modify the input array in-place. We allocate exactly two integer primitives (maxRight and currentVal) regardless of how large the input array is. Therefore, auxiliary space is strictly \(O(1)\).
Code¶
class Solution {
public int[] replaceElements(int[] arr) {
if (arr == null || arr.length == 0) {
return arr;
}
int maxRight = -1;
for (int i = arr.length - 1; i >= 0; i--) {
// Cache the current element before overwriting
int currentVal = arr[i];
// Replace with the greatest element seen so far from the right
arr[i] = maxRight;
// Update the max for the next iteration (moving left)
maxRight = Math.max(maxRight, currentVal);
}
return arr;
}
}
Caveats¶
- Immutability Constraints: In some strict functional programming environments or multi-threaded contexts, mutating the input array (
arr[i] = ...) is an anti-pattern or expressly forbidden. In such cases, you must sacrifice \(O(1)\) space and allocate a new result array of size \(N\). - Stream Processing: If the array is arriving as an unbounded stream (e.g., live stock prices) where you must output the answer for day \(i\) immediately before seeing day \(i+1\), this reverse-traversal approach is physically impossible.
Concepts to Think About¶
- State Propagation: Notice how the state (
maxRight) strictly propagates in one direction. This is a foundational building block for 1D Dynamic Programming. - In-Place Swapping Mechanics: The
tempvariable pattern used here is universal. Whenever you need to read and write to the same memory location where the old value influences future writes, a temporary cache is required. - Cache Locality: Reverse iteration has the exact same highly efficient cache locality as forward iteration. The CPU prefetches the contiguous memory blocks backward just as effectively.
Logical Follow-up¶
Question: Find all the "Leaders" in an array. An element is a leader if it is strictly greater than all the elements to its right side. Solution: We use the exact same reverse-traversal pattern. Initialize maxRight = -infinity. Iterate from right to left. If arr[i] > maxRight, it is a leader; add it to the result list, and update maxRight = arr[i]. This solves the problem in \(O(N)\) time and \(O(1)\) space (excluding the output list).
Question: What if you needed to replace every element with the next strictly greater element to its right (not the absolute maximum), and -1 if none exists? (Next Greater Element) Solution: A single state variable no longer works because the "next greater" isn't necessarily the global maximum. We must use a Monotonic Stack. Iterate right-to-left. While the stack is not empty and the top of the stack is \(\le arr[i]\), pop it. If the stack becomes empty, the answer is -1. Otherwise, the answer is the top of the stack. Push \(arr[i]\) to the stack. This requires \(O(N)\) time and \(O(N)\) space.