Problem overview
The function max_sum_of_three_subarrays receives a vector of integers, nums, with at least three elements. It must pick three non-empty, non-overlapping subarrays (runs of consecutive elements) whose combined sum is as large as possible, and return that sum. Numbers may be negative.
In the example, nums = [2, 3, -8, 7, -2, 9, -9, 7, -2, 4]. The subarrays [2, 3], [7, -2, 9] and [7, -2, 4] add up to 5 + 14 + 9 = 28, the best possible.
Approach: a linear pass over the end of the middle subarray
The natural way to solve this is to fix the middle subarray, from k to i. The first subarray is then the best one inside the prefix before k, and the third is the best one inside the suffix after i. Two tables give those answers in constant time:
left_max_sum[x]: the maximum subarray sum insidenums[0..=x];right_max_sum[x]: the maximum subarray sum insidenums[x..].
Even so, trying every pair (k, i) is O(n²). This lesson removes the loop over k with a rewrite. Let P[x] be the sum of nums[0..=x]. The middle subarray from k to i sums to P[i] - P[k - 1], so the total is
left_max_sum[k - 1] + (P[i] - P[k - 1]) + right_max_sum[i + 1]
= P[i] + right_max_sum[i + 1] + (left_max_sum[k - 1] - P[k - 1])
The last bracket depends only on k, and the first two terms only on i. For a fixed i, the best k is the one that maximizes the bracket among all starts k up to i. Call that maximum max_diff. As i moves right, one new start becomes available, so max_diff is updated with one comparison instead of a loop.
In the example, take i = 5, the end of [7, -2, 9]. Here P[5] = 11 and right_max_sum[6] = 9. The best start is k = 3, where left_max_sum[2] - P[2] = 5 - (-3) = 8. The total is 11 + 9 + 8 = 28.
Step by step
- Store
nums.len()inn. - Create
left_max_sumas a mutable vector ofnzeros of typei32and seedleft_max_sum[0]withnums[0]. - Declare
cur, the best sum of a subarray ending at the current index, starting atnums[0]. - Loop over
ifrom 1 ton - 1. Updatecurtonums[i] + cur.max(0): extend the previous run if it is positive, start fresh otherwise. - Fill the table:
left_max_sum[i]is the larger ofleft_max_sum[i - 1]andcur. The best subarray in the prefix either ends atior lies before it. - Create
right_max_sumthe same way, seed its last entry withnums[n - 1], and resetcurtonums[n - 1], now the best sum of a subarray starting at the current index. - Loop from
n - 2down to 0 with(0..n - 1).rev(), updatingcurwith the same formula and settingright_max_sum[i]to the larger ofright_max_sum[i + 1]andcur. - Declare
max_sumasi32::MIN, because the answer can be negative. - Declare
partial_sum, starting atnums[0]. At the start of each pass overiit holds the sum of everything before indexi. - Declare
max_diffati32::MIN. It will hold the largest value ofleft_max_sum[k - 1]minus the sum beforek, over all the startskseen so far. - Loop over
i, the last index of the middle subarray, from 1 ton - 2. It needs a first subarray before it and a third after it. - Offer index
ias a start:max_diffbecomes the larger of itself andleft_max_sum[i - 1] - partial_sum. At this pointpartial_sumis still the sum beforei, as the formula needs. This must happen before the next step, because the middle subarray can consist of the single elementnums[i]. - Add
nums[i]topartial_sum, so it now holdsP[i]. - Update the answer:
max_sumbecomes the larger of itself andpartial_sum + right_max_sum[i + 1] + max_diff. - Return
max_sumas the last expression.
The solution in Rust
impl Solution {
fn max_sum_of_three_subarrays(nums: Vec<i32>) -> i32 {
let n = nums.len();
let mut left_max_sum = vec![0i32; n];
left_max_sum[0] = nums[0];
let mut cur = nums[0];
for i in 1..n {
cur = nums[i] + cur.max(0);
left_max_sum[i] = left_max_sum[i - 1].max(cur);
}
let mut right_max_sum = vec![0i32; n];
right_max_sum[n - 1] = nums[n - 1];
cur = nums[n - 1];
for i in (0..n - 1).rev() {
cur = nums[i] + cur.max(0);
right_max_sum[i] = right_max_sum[i + 1].max(cur);
}
let mut max_sum = i32::MIN;
let mut partial_sum = nums[0];
let mut max_diff = i32::MIN;
for i in 1..n - 1 {
max_diff = max_diff.max(left_max_sum[i - 1] - partial_sum);
partial_sum += nums[i];
max_sum = max_sum.max(partial_sum + right_max_sum[i + 1] + max_diff);
}
max_sum
}
}
The Rust details worth a closer look:
vec![0i32; n]. It builds a vector ofncopies of 0, and the suffix fixes the element type toi32.(0..n - 1).rev(). Ranges only count upwards, so a descending loop reverses an ascending one. The range excludes its end, so this visitsn - 2down to 0.- Reusing
cur. The second use is a plain assignment,cur = nums[n - 1], with nolet, because the variable already exists and is mutable. - Safe unsigned subtraction. The indices are
usize. Expressions such asi - 1are safe because the loops start at 1, andn - 1is safe becausenis at least 3. Below zero, a debug build panics and an optimized build wraps. i32::MINas a start.max_diffis always replaced before it is used, since the first pass sets it from a real value, so the sumpartial_sum + ... + max_diffnever addsi32::MIN.- Wrapping. With optimizations,
i32overflow wraps silently. The code assumes sums that fit in ani32.
Complexity
Time: O(n). Two passes build the tables and one pass combines them. Each pass does constant work per element.
Space: O(n). The two tables hold n integers each. Everything else is a handful of scalars.
Edge cases and common mistakes
- All numbers negative. For
[-1, -2, -3]the answer is -6, and for[-5, -1, -3, -2]it is -6 as well. Startingmax_sumat 0 would give a wrong answer. - Exactly three numbers. The loop runs once, with
i = 1, and the three single elements are the only choice. - Swapping the two updates. If
partial_sum += nums[i]came before themax_diffupdate, the new startiwould be offered with the sum includingnums[i], which is the wrong prefix. - Using the wrong bounds. The loop ends at
n - 2so thatright_max_sum[i + 1]exists. A bound ofnwould index past the end. - Mixed signs. For
[4, -1, 4, -1, 4]the answer is 12, three separate 4s. For[1, 2, 3, 4]it is 10. - Not checking against a slower version. A rewrite like this one is easy to get subtly wrong. Compare it against the version with two loops on small random inputs.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives 28.[-1, -2, -3]gives -6.[-5, -1, -3, -2]gives -6.[1, 2, 3, 4]gives 10.[4, -1, 4, -1, 4]gives 12.
