Kadane's Algorithm with Indices
Interview Prep
The problem
Same as Maximum Subarray, but return the start and end indices of the optimal subarray — not just the sum. Interviewers love this follow-up because it distinguishes candidates who memorised a 4-line Kadane from those who understand the recurrence.
Pattern: track the start when you restart
The standard Kadane recurrence is
current = max(nums[i], current + nums[i]). The
decision boils down to "restart" vs "extend":
- If
nums[i] > current + nums[i], restart — the previous segment was a net liability. Record this position as the new candidate start. - Otherwise extend — the existing segment is worth keeping.
Two index variables, both updated alongside current:
cur_lo (start of the segment currently being built) and
the pair (best_lo, best_hi) (the best segment seen so
far). Update best only when current beats
it, and snap best_lo to whatever cur_lo
is now.
Solution
def max_subarray_with_indices(nums: list[int]) -> tuple[int, int, int]:
"""Return (best_sum, lo, hi) — the indices of the optimal subarray (inclusive)."""
best = current = nums[0]
best_lo = best_hi = 0
cur_lo = 0
for i in range(1, len(nums)):
x = nums[i]
if x > current + x:
current = x # restart — start a fresh subarray at i
cur_lo = i
else:
current = current + x # extend the current subarray
if current > best:
best = current
best_lo, best_hi = cur_lo, i
return best, best_lo, best_hi Trace
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
i=0 (init): current=-2, best=-2, best_lo=0, best_hi=0, cur_lo=0
i=1, x=1: 1 > -2+1=-1 -> restart. current=1, cur_lo=1.
1 > -2 -> best=1, best_lo=1, best_hi=1.
i=2, x=-3: -3 > 1-3=-2? no -> extend. current=-2. best unchanged.
i=3, x=4: 4 > -2+4=2 -> restart. current=4, cur_lo=3.
4 > 1 -> best=4, best_lo=3, best_hi=3.
i=4, x=-1: -1 > 4-1=3? no -> extend. current=3. best unchanged.
i=5, x=2: 2 > 3+2=5? no -> extend. current=5.
5 > 4 -> best=5, best_lo=3, best_hi=5.
i=6, x=1: extend. current=6. best=6, best_hi=6.
i=7, x=-5: extend. current=1. best unchanged.
i=8, x=4: 4 > 1+4=5? no -> extend. current=5. best unchanged.
return (6, 3, 6) subarray [4, -1, 2, 1] Edge case: all-negative array
A standard gotcha: if every element is negative, the optimal
"subarray" is the single largest (least negative) element. The
recurrence above handles this naturally because we initialize
best and current from nums[0]
and never let an empty subarray sneak in.
# Edge case: all elements negative.
# nums = [-3, -1, -4]
# i=0: current=-3, best=-3, lo=hi=0, cur_lo=0
# i=1, x=-1: -1 > -3 + -1 = -4 -> restart. current=-1, cur_lo=1.
# -1 > -3 -> best=-1, lo=1, hi=1.
# i=2, x=-4: -4 > -1 + -4 = -5? yes -> restart. current=-4, cur_lo=2.
# -4 > -1? no -> best unchanged.
# return (-1, 1, 1) single-element subarray [-1] ✓ Complexity
- Time:
O(n). - Space:
O(1).
Strict equality and ties
Using strict > when comparing
nums[i] to current + nums[i] means
that on ties we extend rather than restart. This
biases the result toward the longest subarray achieving the optimal
sum. If the spec wants the shortest, flip to ≥ in the
restart condition. Worth asking the interviewer.