Problem overview
You get an IntArray of non-negative numbers and a target. Find a continuous subarray whose elements add up to target and return its first and last index as a two-element IntArray. If several subarrays work, any one is accepted.
For [1, 1, 5, 2, 1, 3, 10, 2, 1] with a target of 21, the answer is [2, 6], because 5 + 2 + 1 + 3 + 10 = 21. The lesson asks for O(n) time and O(1) extra space.
The solution returns [-1, -1] when no subarray adds up to the target.
Approach
The brute force tries every pair of ends and adds up the elements between them, which is O(n^3), or O(n^2) if you keep running sums. Both are too slow for the required O(n).
The key fact is that all numbers are non-negative. Adding an element to a subarray never lowers its sum, and removing one never raises it. That gives a simple rule for a window [j...i]:
- if the sum is too big, the only fix is to shrink the window from the left;
- if the sum is too small, extend the window to the right.
The right end i moves once through the array, and the left end j only moves to the right. Neither goes back, so the work is linear. The only extra storage is the two indices and the running sum, so the space is constant. This is what makes the window better than a hash map of prefix sums, which would need O(n) space.
If the array could hold negative numbers, the rule would break: removing an element could make the sum bigger, and the window couldn't decide which way to move.
Step by step
- Two edges and a total.
jandsumstart at 0. They arevars because the loop updates them. The window is[j...i]andsumis always the total of its elements. - Loop over the right end. The
forloop overnums.indicesmakes each element in turn the new right end. - Add the new element.
sum += nums[i]keepssumin step with the window. - Shrink while too big. The inner
whileloop runs whilej < iandsum > target. The checkj < ikeeps at leastnums[i]in the window. A single element that is larger than the target stays in the window, and the sum stays too big until the next element arrives andjpasses over it. - Drop the left element. Inside the loop,
nums[j]is subtracted fromsumand thenjis incremented. The order matters: subtract first, whilejstill points to the element being dropped. - Check for a match. After the inner loop, if
sum == targetthe window is a solution, so returnintArrayOf(j, i)right away. - Nothing found. After the
forloop, returnintArrayOf(-1, -1).
The solution in Kotlin
class Solution {
fun subarraySum(nums: IntArray, target: Int): IntArray {
var j = 0
var sum = 0
for (i in nums.indices) {
sum += nums[i]
while (j < i && sum > target) {
sum -= nums[j]
j++
}
if (sum == target) {
return intArrayOf(j, i)
}
}
return intArrayOf(-1, -1)
}
}
Kotlin details worth knowing:
intArrayOf(j, i)builds anIntArray, the primitiveint[]of the JVM. AListorPairwould not match the declared return type.for (i in nums.indices)gives you the index directly, which you need here because the answer is made of indices, not values.sumis anInt, a 32-bit number whose overflow wraps silently. The lesson's inputs are small, but a very large array of large values could overflow it; aLongwould be the safe type in that case.
Following the example
With nums = [1, 1, 5, 2, 1, 3, 10, 2, 1] and a target of 21, the window grows while the sum stays at or below 21. After i = 5 the sum is 1 + 1 + 5 + 2 + 1 + 3 = 13. Adding 10 at i = 6 makes it 23, which is more than 21. The inner loop subtracts nums[0] (sum 22, j = 1) and then nums[1] (sum 21, j = 2). Now sum == target, so the function returns [2, 6].
Complexity
Time: O(n). The outer loop runs n times. The inner loop moves j forward by one on each pass, and j never goes back, so across the whole run it runs at most n times in total.
Space: O(1). Only j, i and sum are stored, plus the two-element result.
Edge cases and common mistakes
- No match. For
[1, 2, 3]with a target of 7 the function returns[-1, -1]. - Zeros. Zeros are non-negative, so they are allowed. For
[0, 0, 4]with a target of 0 the function returns[0, 0]: the first zero alone is already a valid subarray. - A single element. If one element equals the target, the window is that element alone and the result has
j == i. - Dropping the
j < icheck. Without it, the window could become empty, andjcould run pasti. - Returning values instead of indices. The answer is the positions of the first and last elements, not the elements.
- Using
ifinstead ofwhileto shrink. A big new element can require dropping several old ones, so one step is not enough.
Testing
[1, 1, 5, 2, 1, 3, 10, 2, 1], target 21:[2, 6].[1, 2, 3], target 7:[-1, -1].[0, 0, 4], target 0:[0, 0].