Dynamic Programming · 36 of 60
Smallest sum contiguous subarray
Find the run of days in a row where your sticker count dropped the most, which is the stretch of an array with the smallest sum.
- Simple way
- time O(n^2)
room O(1) - Clever way
- time O(n)
room O(1)
The problem, simply
You get a list of whole numbers, some positive and some negative. Look at every stretch of numbers that sit next to each other (at least one number). Give back the smallest sum any stretch can have.
It is the mirror image of the famous "largest sum stretch" problem.
A story to remember it
Picture it
Days 1 to 4 add up to -4 + 2 - 3 - 1 = -6. No other stretch is smaller. Note that the stretch keeps the +2 inside, because the numbers around it make up for it.
- answer
Examples
Mixed numbers
- In
[3, -4, 2, -3, -1, 7, -5]- Out
-6
The stretch -4, 2, -3, -1.
All positive
- In
[2, 6, 8, 1, 4]- Out
1
Adding more positive numbers only makes the sum bigger, so the best stretch is the single smallest number.
All negative
- In
[-1, -2, -3]- Out
-6
Every number pulls the sum down, so take them all.
One number
- In
[5]- Out
5
The only stretch is the number itself.
Ties
- In
[1, -1, 1, -1]- Out
-1
Several stretches reach -1, such as [-1] and [-1, 1, -1]. Only the sum is asked.
Way 1: the simple way
Try every start. From each start, walk right and keep a running sum. Every time the running sum is a new low, remember it.
- For each start
i, setsum = 0. - For each end
jfromionward, adda[j]and comparesumwith the smallest seen.
Starting at day 1 and walking right: -4, then -2, then -5, then -6, then 1, then -4. The low point -6 is at day 4. Then we start again at day 2, and so on.
class Solution {
public int smallestSumSubarray(int[] a) {
int best = Integer.MAX_VALUE;
for (int i = 0; i < a.length; i++) { // every start
int sum = 0;
for (int j = i; j < a.length; j++) { // every end
sum += a[j];
best = Math.min(best, sum);
}
}
return best;
}
}How long it takes: about n * n / 2 stretches, O(n^2). Extra room: two numbers, O(1).
Way 2: the clever way
Keep the worst streak that ends today, and the worst streak ever.
- Start both at
a[0]. - For each next number
x:endHere = min(x, endHere + x). Either start fresh withx, or carry yesterday's streak. best = min(best, endHere).
Each endHere only needs the one before it, so this is a tiny DP with one box of memory.
One pass over the days, keeping the worst streak ending here
01 / 08
Day 0: the only stretch ending here is 3 itself. Smallest so far is 3.
- ending here
- 3
- smallest
- 3
- looking now
- checking
- answer
- just added
- ignored
class Solution {
public int smallestSumSubarray(int[] a) {
int endHere = a[0]; // smallest sum of a stretch ending at this day
int best = a[0]; // smallest sum seen anywhere
for (int i = 1; i < a.length; i++) {
endHere = Math.min(a[i], endHere + a[i]); // start fresh or carry on
best = Math.min(best, endHere);
}
return best;
}
}How long it takes: one walk, O(n). Extra room: two numbers, O(1).
Watch out for
- The stretch must be at least one number long. An empty stretch with sum 0 does not count.
- Resetting to 0 instead of to
a[i]. The textbook "reset when positive" version works only if you compare before resetting.min(a[i], endHere + a[i])avoids the trap. - Very large sums can overflow
int. Uselongif numbers can be near a billion.
What interviewers ask next
- Can you reuse the largest sum code? Yes. Flip the sign of every number, find the largest sum stretch, and flip the answer back.
- Return the start and end of the stretch. Remember where the current streak started each time you start fresh, and copy it when you find a new best.
- Maximum sum in a circular array (LeetCode 918). The best wrap around stretch is
total - smallest stretch. So you need exactly this problem. - Smallest sum stretch of length at least
k. Use running totals (prefix sums) and keep the largest prefix that is at leastkpositions back.
Remember this
Grown-up words: a stretch is a contiguous subarray, the worst streak ending today is the DP state minEndingHere, the whole trick is Kadane's algorithm, and running totals are prefix sums.