Reverse Subarray To Maximize Array Value
HardPrompt
You are given an integer array nums. The value of this array is defined as the sum of |nums[i] - nums[i + 1]| for all 0 <= i < nums.length - 1.
You are allowed to select any subarray of the given array and reverse it. You can perform this operation only once.
Find maximum possible value of the final array.
Example 1:
Input: nums = [2,3,1,5,4]
Output: 10
Explanation: By reversing the subarray [3,1,5] the array becomes [2,5,1,3,4] whose value is 10.Example 2:
Input: nums = [2,4,9,24,2,1,10]
Output: 68
Constraints:
2 <= nums.length <= 3 * 104-105 <= nums[i] <= 105- The answer is guaranteed to fit in a 32-bit integer.
Approaches
3 approaches with complexity analysis and trade-offs.
The most straightforward approach is to simulate the process directly. We can try reversing every possible subarray, calculate the resulting array's value, and keep track of the maximum value seen. This involves three nested loops: two to define the subarray's start and end points, and a third to calculate the value of the array after the reversal.
Algorithm
- Initialize
maxValueto 0. - Generate every possible subarray
nums[i...j]by iteratingifrom0ton-1andjfromiton-1. - For each subarray:
a. Create a temporary copy of the
numsarray. b. Reverse the subarray from indexitojin the temporary array. c. Calculate the 'value' of this modified temporary array by summing the absolute differences of adjacent elements:sum(|temp[k] - temp[k+1]|). d. UpdatemaxValue = max(maxValue, calculated_value). - Return
maxValue.
Walkthrough
This brute-force method iterates through all possible start (i) and end (j) indices of a subarray. For each subarray nums[i...j], it creates a copy of the original array, performs the reversal on the copy, and then computes the value of the new array from scratch. The value is the sum of absolute differences of adjacent elements. The maximum value found across all possible reversals is the answer.
class Solution { public int maxValueAfterReverse(int[] nums) { int n = nums.length; int maxVal = 0; // Calculate initial value to handle the case of no reversal (or reversing a single element) for (int i = 0; i < n - 1; i++) { maxVal += Math.abs(nums[i] - nums[i + 1]); } for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { // Create a temporary array and reverse the subarray [i, j] int[] temp = new int[n]; System.arraycopy(nums, 0, temp, 0, n); reverse(temp, i, j); // Calculate the value of the new array int currentVal = 0; for (int k = 0; k < n - 1; k++) { currentVal += Math.abs(temp[k] - temp[k + 1]); } maxVal = Math.max(maxVal, currentVal); } } return maxVal; } private void reverse(int[] arr, int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } }}Complexity
Time
O(N³), where N is the number of elements in the array. There are O(N²) possible subarrays. For each, copying the array takes O(N), reversing takes O(j-i) which is O(N), and calculating the new value takes O(N). This leads to an overall complexity of O(N² * N) = O(N³).
Space
O(N), where N is the number of elements in the array. This is for storing the temporary copy of the array in each iteration.
Trade-offs
Pros
Simple to understand and implement.
Cons
Extremely inefficient due to its cubic time complexity.
Not feasible for the given constraints on the input size (
nup to 3 * 10^4).
We can improve upon the brute-force approach by observing that when we reverse a subarray nums[i...j], the sum of absolute differences for elements outside this subarray and for elements completely inside this subarray remains unchanged. The only change in the total value comes from the connections at the boundaries of the subarray. This allows us to calculate the change in value in O(1) time for each subarray, reducing the overall complexity.
Algorithm
- Calculate the initial value of the array,
baseValue = sum(|nums[i] - nums[i+1]|). - Initialize
maxGain = 0. - Iterate through all possible start indices
ifrom0ton-1. - For each
i, iterate through all possible end indicesjfromiton-1. - For each pair
(i, j), calculate the change in value (gain) that would result from reversingnums[i...j]. This can be done in O(1) by only considering the boundaries of the subarray.- If the subarray is internal (
0 < i <= j < n-1), the gain is|nums[i-1] - nums[j]| + |nums[i] - nums[j+1]| - |nums[i-1] - nums[i]| - |nums[j] - nums[j+1]|. - Handle edge cases where the subarray touches the array's start or end.
- If the subarray is internal (
- Update
maxGain = max(maxGain, gain). - The final answer is
baseValue + maxGain.
Walkthrough
First, we compute the initial value of the array. Then, we iterate through all possible subarrays nums[i...j]. Instead of creating a new array and recalculating the entire sum, we calculate the 'gain' directly.
When a subarray nums[i...j] is reversed, the pairs of adjacent elements that change are at the boundaries. The original boundary pairs are (nums[i-1], nums[i]) and (nums[j], nums[j+1]). After reversal, they become (nums[i-1], nums[j]) and (nums[i], nums[j+1]). The change in value is the difference in the sum of absolute differences of these pairs. We calculate this gain for every subarray and find the maximum possible gain. Special care is needed for subarrays that start at index 0 or end at index n-1.
class Solution { public int maxValueAfterReverse(int[] nums) { int n = nums.length; int baseValue = 0; for (int i = 0; i < n - 1; i++) { baseValue += Math.abs(nums[i] - nums[i + 1]); } int maxGain = 0; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { int currentGain = 0; if (i > 0 && j < n - 1) { // Internal subarray int originalBoundarySum = Math.abs(nums[i - 1] - nums[i]) + Math.abs(nums[j] - nums[j + 1]); int newBoundarySum = Math.abs(nums[i - 1] - nums[j]) + Math.abs(nums[i] - nums[j + 1]); currentGain = newBoundarySum - originalBoundarySum; } else if (i == 0 && j < n - 1) { // Subarray starts at the beginning int originalBoundarySum = Math.abs(nums[j] - nums[j + 1]); int newBoundarySum = Math.abs(nums[0] - nums[j + 1]); currentGain = newBoundarySum - originalBoundarySum; } else if (i > 0 && j == n - 1) { // Subarray ends at the end int originalBoundarySum = Math.abs(nums[i - 1] - nums[i]); int newBoundarySum = Math.abs(nums[i - 1] - nums[n - 1]); currentGain = newBoundarySum - originalBoundarySum; } // If i=0 and j=n-1, gain is 0. maxGain = Math.max(maxGain, currentGain); } } return baseValue + maxGain; }}Complexity
Time
O(N²), where N is the number of elements. We have two nested loops to iterate through all O(N²) subarrays, and the calculation for each is O(1).
Space
O(1), as we are not using any extra space that scales with the input size.
Trade-offs
Pros
Significantly faster than the O(N³) approach.
Avoids creating new arrays in memory for each subarray.
Cons
Still too slow for the given constraints, as O(N²) is not efficient enough.
A linear time solution can be achieved by analyzing the formula for the gain in value. The problem can be broken down into three cases for the reversed subarray: it touches the left end, it touches the right end, or it's in the middle. The first two cases can be solved with simple linear scans. The third, most complex case, can also be optimized to a linear scan through a mathematical insight.
Algorithm
- Calculate the initial
baseValueof the array. - Initialize
maxGain = 0. - Handle reversals touching an endpoint:
a. Iterate
ifrom1ton-1. The gain from reversing[0...i-1]is|nums[i] - nums[0]| - |nums[i] - nums[i-1]|. UpdatemaxGain. b. Iterateifrom0ton-2. The gain from reversing[i+1...n-1]is|nums[i] - nums[n-1]| - |nums[i] - nums[i+1]|. UpdatemaxGain. - Handle internal reversals
[i...j]: a. The gain is|nums[i-1]-nums[j]| + |nums[i]-nums[j+1]| - |nums[i-1]-nums[i]| - |nums[j]-nums[j+1]|. b. This gain is maximized when the intervals[min(nums[k], nums[k+1]), max(nums[k], nums[k+1])]and[min(nums[l], nums[l+1]), max(nums[l], nums[l+1])]are disjoint and far apart, wherek=i-1andl=j. c. The maximum gain from this case is2 * max(max_v - min_u, 0), wheremax_vis the maximum of allmin(nums[k], nums[k+1])andmin_uis the minimum of allmax(nums[k], nums[k+1]). d. We can find this maximum gain in a single pass. Initializemax_v = -infinityandmin_u = +infinity. Iteratekfrom0ton-2, updatingmax_vandmin_uand the potential gain at each step. - Update
maxGainwith the gain from the internal reversal case. - The final answer is
baseValue + maxGain.
Walkthrough
The core idea is to find the maximum possible gain over the baseValue and add it. The gain depends on where the reversed subarray [i, j] is located.
-
Initial Value: First, calculate the
baseValue = sum(|nums[k] - nums[k+1]|)forkfrom0ton-2. -
Reversal at Endpoints:
- If we reverse
[0...j], the value changes by|nums[0] - nums[j+1]| - |nums[j] - nums[j+1]|. We can iterate through alljto find the max gain for this case. - If we reverse
[i...n-1], the value changes by|nums[i-1] - nums[n-1]| - |nums[i-1] - nums[i]|. We can iterate through allito find the max gain for this case.
- If we reverse
-
Internal Reversal: For a subarray
[i...j]where0 < i <= j < n-1, the gain is|nums[i-1] - nums[j]| + |nums[i] - nums[j+1]| - |nums[i-1] - nums[i]| - |nums[j] - nums[j+1]|. A key insight is that this expression is maximized when the two original boundary pairs(nums[i-1], nums[i])and(nums[j], nums[j+1])represent value ranges that are far apart. Letu_k = max(nums[k], nums[k+1])andv_k = min(nums[k], nums[k+1]). The gain is maximized by2 * (max(v) - min(u)), wheremax(v)is the maximum of allv_k's andmin(u)is the minimum of allu_k's. We can find this in a single pass.
By combining the maximum gain from all three cases, we can find the overall maxGain in O(N) time.
class Solution { public int maxValueAfterReverse(int[] nums) { int n = nums.length; long baseValue = 0; for (int i = 0; i < n - 1; i++) { baseValue += Math.abs(nums[i] - nums[i + 1]); } int maxGain = 0; // Case 1: Reversing a subarray that includes an endpoint. // This is equivalent to changing one pair (a, b) to (a, c) // where a is an endpoint. for (int i = 1; i < n; i++) { // Reverse [0...i-1]. Change is at boundary i-1, i. // Original: |nums[i-1] - nums[i]|. New: |nums[0] - nums[i]|. maxGain = Math.max(maxGain, Math.abs(nums[i] - nums[0]) - Math.abs(nums[i] - nums[i-1])); } for (int i = 0; i < n - 1; i++) { // Reverse [i+1...n-1]. Change is at boundary i, i+1. // Original: |nums[i] - nums[i+1]|. New: |nums[i] - nums[n-1]|. maxGain = Math.max(maxGain, Math.abs(nums[i] - nums[n-1]) - Math.abs(nums[i] - nums[i+1])); } // Case 2: Reversing an internal subarray [i...j]. // Gain = |nums[i-1]-nums[j]| + |nums[i]-nums[j+1]| - |nums[i-1]-nums[i]| - |nums[j]-nums[j+1]| // This is maximized by 2 * (max(min_pair) - min(max_pair)) int min_u = Integer.MAX_VALUE; int max_v = Integer.MIN_VALUE; for (int i = 0; i < n - 1; i++) { int u = Math.max(nums[i], nums[i + 1]); int v = Math.min(nums[i], nums[i + 1]); min_u = Math.min(min_u, u); max_v = Math.max(max_v, v); } int internalGain = 2 * (max_v - min_u); maxGain = Math.max(maxGain, internalGain); return (int) (baseValue + maxGain); }}Complexity
Time
O(N), where N is the number of elements. The algorithm consists of a few separate linear passes over the array.
Space
O(1), as it only uses a few variables to store state.
Trade-offs
Pros
Highly efficient with linear time complexity.
Optimal solution for the given problem constraints.
Cons
The logic is complex and relies on a non-obvious mathematical insight about the gain formula.
Solutions
Solution
class Solution {public int maxValueAfterReverse(int[] nums) { int n = nums.length; int s = 0; for (int i = 0; i < n - 1; ++i) { s += Math.abs(nums[i] - nums[i + 1]); } int ans = s; for (int i = 0; i < n - 1; ++i) { ans = Math.max(ans, s + Math.abs(nums[0] - nums[i + 1]) - Math.abs(nums[i] - nums[i + 1])); ans = Math.max(ans, s + Math.abs(nums[n - 1] - nums[i]) - Math.abs(nums[i] - nums[i + 1])); } int[] dirs = {1, -1, -1, 1, 1}; final int inf = 1 << 30; for (int k = 0; k < 4; ++k) { int k1 = dirs[k], k2 = dirs[k + 1]; int mx = -inf, mi = inf; for (int i = 0; i < n - 1; ++i) { int a = k1 * nums[i] + k2 * nums[i + 1]; int b = Math.abs(nums[i] - nums[i + 1]); mx = Math.max(mx, a - b); mi = Math.min(mi, a + b); } ans = Math.max(ans, s + Math.max(0, mx - mi)); } return ans; }}class Solution {public: int maxValueAfterReverse(vector<int> &nums) { int n = nums.size(); int s = 0; for (int i = 0; i < n - 1; ++i) { s += abs(nums[i] - nums[i + 1]); } int ans = s; for (int i = 0; i < n - 1; ++i) { ans = max(ans, s + abs(nums[0] - nums[i + 1]) - abs(nums[i] - nums[i + 1])); ans = max(ans, s + abs(nums[n - 1] - nums[i]) - abs(nums[i] - nums[i + 1])); } int dirs[5] = {1, -1, -1, 1, 1}; const int inf = 1 << 30; for (int k = 0; k < 4; ++k) { int k1 = dirs[k], k2 = dirs[k + 1]; int mx = -inf, mi = inf; for (int i = 0; i < n - 1; ++i) { int a = k1 * nums[i] + k2 * nums[i + 1]; int b = abs(nums[i] - nums[i + 1]); mx = max(mx, a - b); mi = min(mi, a + b); } ans = max(ans, s + max(0, mx - mi)); } return ans; }};class Solution: def maxValueAfterReverse(self, nums: List[int]) -> int: ans = s = sum(abs(x - y) for x, y in pairwise(nums)) for x, y in pairwise(nums): ans = max(ans, s + abs(nums[0] - y) - abs(x - y)) ans = max(ans, s + abs(nums[- 1] - x) - abs(x - y)) for k1, k2 in pairwise((1, - 1, - 1, 1, 1)): mx, mi = - inf, inf for x, y in pairwise(nums): a = k1 * x + k2 * y b = abs(x - y) mx = max(mx, a - b) mi = min(mi, a + b) ans = max(ans, s + max(mx - mi, 0)) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.