What partial sums are
Suppose you have an array of numbers and you need the sum of many different slices of it. Adding the elements of a slice from scratch each time costs time proportional to the slice length. If there are q queries over an array of n numbers, that is up to O(n * q) work.
Partial sums, also called prefix sums, remove the repeated work. You make one pass over the array and store, for each position, the sum of everything before it. After that, any slice sum is a single subtraction.
Building the prefix array
Let nums have n elements. Make an array prefix with n + 1 elements, where prefix(k) is the sum of the first k elements. So prefix(0) is 0, the sum of nothing, and prefix(n) is the sum of the whole array:
val nums = Array(3, 1, 4, 1, 5, 9, 2, 6)
val prefix = new Array[Int](nums.length + 1)
for (i <- nums.indices) {
prefix(i + 1) = prefix(i) + nums(i)
}
println(prefix.mkString(", "))
0, 3, 4, 8, 9, 14, 23, 25, 31
Several Scala details are at work here:
new Array[Int](n)creates an array of n integers, all starting at 0. That is whyprefix(0)is already correct.- Elements are read and written with parentheses:
prefix(i + 1) = .... nums.indicesis the range0 until nums.length.- Printing an array with
printlnshows an object reference, somkStringturns it into readable text.
The standard library can build the same array in one call. nums.scanLeft(0)(_ + _) starts from 0 and adds each element to the running total, producing all the partial sums, and it gives the same nine values as the loop above. The loop is the version to practise, because it shows the recurrence: each entry is the previous entry plus one element.
Answering a range query
To get the sum of nums(l) through nums(r), both included, subtract two prefix values:
def rangeSum(l: Int, r: Int): Int = prefix(r + 1) - prefix(l)
println(rangeSum(2, 5)) // 4 + 1 + 5 + 9
println(rangeSum(0, 7)) // the whole array
println(rangeSum(3, 3)) // a single element
19
31
1
The reasoning: prefix(r + 1) is the sum of everything up to and including index r, and prefix(l) is the sum of everything before index l. Subtracting leaves exactly the elements from l to r. The extra leading zero in the array means the case l = 0 needs no special handling, because prefix(0) is 0.
Building the array costs O(n) time and O(n) space. Every query afterwards costs O(1), so q queries cost O(n + q) altogether.
Common mistakes
- Off-by-one in the formula. With the leading zero, the range
l..risprefix(r + 1) - prefix(l). Usingprefix(r) - prefix(l)leaves outnums(r). - Overflow. An
Intholds about 2.1 billion at most, and an overflow wraps silently.Array(2000000000, 2000000000).sumgives -294967296. When sums can grow large, store the prefix array asLong:new Array[Long](n + 1). Adding anIntto aLongwidens the result automatically, sop(i + 1) = p(i) + nums(i)compiles and gives 4000000000 for that input. - Recomputing the sum for each query. A loop with
slice(l, r + 1).sumreturns the same answers but costsO(r - l)per query. - Confusing exclusive and inclusive ends. Scala's
slice(from, until)excludes its end, while the formula above takes an inclusive endr. Decide which convention you use and keep it.
Key takeaways
- A prefix array of size n + 1 stores the sum of the first k elements at index k, with
prefix(0) = 0. - Build it once in
O(n); then each range sum isprefix(r + 1) - prefix(l)inO(1). - In Scala, use
new Array[Int](n + 1)orscanLeft, index with parentheses and switch toLongwhen sums can pass about two billion. - The technique fits any task that asks many times for the total of a slice of a fixed array.
