Problem overview
The method removeMinSum receives an Array[Int] called nums. You pick some numbers from the beginning of the array and some numbers from the end, and you want the total of the picked numbers to be as small as possible. The method returns that minimum total.
Picking nothing is allowed, and then the total is 0. Picking every number is allowed too. A number can't be picked twice, so the picks from the front and the picks from the back can't overlap.
For nums = [-2, 5, 2, -1, 3, -10, 9, -2] the answer is -5. One way to get it is to take the first element and the last three: -2 + (-10) + 9 + (-2) = -5. Another is to take the first six and the last one: -2 + 5 + 2 - 1 + 3 - 10 - 2 = -5.
Approach: combine prefix sums with suffix minimums
A choice is a pair: p numbers from the front and q numbers from the back, with p + q <= n. Its total is prefix(p) + suffix(q), where:
prefix(p)is the sum of the firstpelements, andsuffix(q)is the sum of the lastqelements.
Trying every pair would take O(n^2) time. The faster idea fixes p and asks for the best q directly. For a fixed p, any q from 0 to n - p is allowed, so the best one is the smallest suffix sum among those. Call it suffixMin(n - p), where suffixMin(m) is the smallest of suffix(0), suffix(1), ..., suffix(m). This table is built once, in a single pass, because each entry is the smaller of the previous entry and the new suffix sum.
Then one loop over p from 0 to n takes the minimum of prefix(p) + suffixMin(n - p). Each of the tables has n + 1 entries, where entry 0 stands for picking nothing.
On the example, the prefix sums are 0, -2, 3, 5, 4, 7, -3, 6, 4, and the suffix sums are 0, -2, 7, -3, 0, -1, 1, 6, 4. The suffix minimums are 0, -2, -2, -3, -3, -3, -3, -3, -3. With p = 1 the front pick is -2 and suffixMin(7) is -3. The total -5 is the answer.
Step by step
- Store the length in
nwithval. - Create
prefixwithArray.fill(n + 1)(0). Entrypwill hold the sum of the firstpelements, and entry 0 stays 0 for "pick nothing". - Fill
prefixin a loop overifrom 0 untiln:prefix(i + 1) = prefix(i) + nums(i). - Create
suffixthe same way. Entryqwill hold the sum of the lastqelements. - Fill
suffixwithsuffix(j + 1) = suffix(j) + nums(n - 1 - j). The elementnums(n - 1 - j)is the(j + 1)-th element counted from the end. - Create
suffixMinas another array ofn + 1zeros. Its entry 0 is 0, the sum of picking no suffix. - Fill
suffixMinforjfrom 1 ton: each entry ismath.min(suffixMin(j - 1), suffix(j)). - Create
resultas avarstarting at 0. Picking nothing is always allowed, so the answer is never above 0. - Loop
pfrom 0 tonand updateresultwithmath.min(result, prefix(p) + suffixMin(n - p)). Withpelements taken from the front, at mostn - premain for the back. - Return
result.
The solution in Scala
class Solution {
def removeMinSum(nums: Array[Int]): Int = {
val n = nums.length
val prefix = Array.fill(n + 1)(0)
for (i <- 0 until n) {
prefix(i + 1) = prefix(i) + nums(i)
}
val suffix = Array.fill(n + 1)(0)
for (j <- 0 until n) {
suffix(j + 1) = suffix(j) + nums(n - 1 - j)
}
val suffixMin = Array.fill(n + 1)(0)
for (j <- 1 to n) {
suffixMin(j) = math.min(suffixMin(j - 1), suffix(j))
}
var result = 0
for (p <- 0 to n) {
result = math.min(result, prefix(p) + suffixMin(n - p))
}
return result
}
}
The Scala details worth knowing:
Array.fill(n + 1)(0)creates an array ofn + 1elements, all 0. The first parentheses hold the size and the second hold the value. The array is mutable, soprefix(i + 1) = ...assigns to an element even thoughprefixis aval. Thevalstops the name from being re-pointed at another array, not the contents from changing.- Assignment looks like a call.
prefix(i + 1) = valueis how Scala writes an array element update. untilversusto. The loops that readnumsuseuntil n, since valid indices stop atn - 1. The loops over then + 1-element tables use1 to nand0 to n, becausenis a valid index in those arrays.- Index arithmetic from the end.
nums(n - 1 - j)walks backwards from the last element asjgrows.
Complexity
Time: O(n). There are four separate loops, each doing a constant amount of work per step.
Space: O(n). Three arrays of n + 1 integers are stored.
Edge cases and common mistakes
- All numbers positive.
[1, 2, 3]gives 0: picking nothing is best. - All numbers negative.
[-1, -2, -3]gives -6, from picking all of them. - A bad number in the middle. For
[5, -3, 5]the answer is 0. The -3 can't be reached without picking a 5 on one side of it. - Overlap. The front and back picks must not share an element. This is why the loop pairs
prefix(p)withsuffixMin(n - p)and not the overall minimum of the suffix table. - The starting value of
result. It starts at 0 because picking nothing is allowed and gives 0. Starting at a value below the real answer, such as a negative constant, would give wrong results. - Forgetting the empty pick. If the tables had no 0 entry at index 0, the case "only from the front" or "only from the back" would be lost.
- An empty array. It gives 0, as all the tables have one entry holding 0.
Testing
[-2, 5, 2, -1, 3, -10, 9, -2]gives-5.[1, 2, 3]gives0.[-1, -2, -3]gives-6.[5, -3, 5]gives0.[3, -4, 6, -1]gives-2.
