๐Ÿฆ€ Functional Rust

798. Kadane's Algorithm: Maximum Subarray Sum

Difficulty: 3 Level: Advanced Find the contiguous subarray with the maximum sum in O(n) โ€” and recover its exact start and end indices.

The Problem This Solves

The maximum subarray problem appears in financial analysis (find the period of greatest cumulative gain in a price series), image processing (find the brightest rectangular region in a 2D intensity map โ€” Kadane in 2D), signal processing (find the strongest burst in a noisy signal), and genomics (find the region of highest GC-content in a sequence). Despite looking simple, the problem has subtle edge cases (all-negative arrays, single elements) that naive brute-force O(nยฒ) solutions handle poorly. Kadane's algorithm solves it exactly in a single pass, making it suitable for streaming data where you can't store the full array.

The Intuition

At each position, you have a choice: extend the current subarray, or start fresh. If `current_sum + x < x`, it means the current prefix is dragging you down โ€” drop it and start a new subarray at `x`. Track the best seen so far. That's it. The entire algorithm fits in one conditional per element. OCaml expresses this elegantly as a fold over the array with an accumulator tuple; Rust uses a `for` loop with `enumerate` to also track indices.

How It Works in Rust

// O(n) time, O(1) space โ€” single pass
fn max_subarray(arr: &[i64]) -> (i64, usize, usize) {
 assert!(!arr.is_empty(), "array must be non-empty");
 let mut best_sum   = arr[0];
 let mut best_start = 0;
 let mut best_end   = 0;
 let mut curr_sum   = arr[0];
 let mut curr_start = 0;

 for (i, &x) in arr.iter().enumerate().skip(1) {
     if x > curr_sum + x {
         // Starting fresh is better than extending
         curr_sum   = x;
         curr_start = i;
     } else {
         curr_sum  += x;
     }
     if curr_sum > best_sum {
         best_sum   = curr_sum;
         best_start = curr_start;
         best_end   = i;
     }
 }
 (best_sum, best_start, best_end)
}

// Example: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
// Answer:  sum=6, subarray=[4, -1, 2, 1] at indices 3..6
The condition `x > curr_sum + x` is equivalent to `curr_sum < 0` but avoids the separate negative-sum check. For all-negative arrays, this correctly returns the least-negative single element. The index tracking adds four extra variables but no asymptotic cost.

What This Unlocks

Key Differences

ConceptOCamlRust
Iteration`Array.fold_left` with tuple accumulator`for (i, &x) in arr.iter().enumerate()`
Start fresh condition`x > curr + x` or `curr < 0`Same condition, identical semantics
Index trackingExtra `ref` variables in foldNamed mutable variables `curr_start`, `best_end`
All-negative caseReturns single element (same logic)Same: `best_sum = arr[0]` initialisation handles it
Return typeTuple `(sum, start, end)`Tuple `(i64, usize, usize)`
// Kadane's Algorithm โ€” maximum subarray sum O(n)

fn max_subarray(arr: &[i64]) -> (i64, usize, usize) {
    assert!(!arr.is_empty(), "array must be non-empty");
    let mut best_sum   = arr[0];
    let mut best_start = 0;
    let mut best_end   = 0;
    let mut curr_sum   = arr[0];
    let mut curr_start = 0;

    for (i, &x) in arr.iter().enumerate().skip(1) {
        if x > curr_sum + x {
            curr_sum   = x;
            curr_start = i;
        } else {
            curr_sum  += x;
        }
        if curr_sum > best_sum {
            best_sum   = curr_sum;
            best_start = curr_start;
            best_end   = i;
        }
    }
    (best_sum, best_start, best_end)
}

fn main() {
    let arr = vec![-2i64, 1, -3, 4, -1, 2, 1, -5, 4];
    let (sum, s, e) = max_subarray(&arr);
    println!("Array:             {:?}", arr);
    println!("Max subarray sum:  {sum}  (indices {s}..{e})");
    println!("Subarray:          {:?}", &arr[s..=e]);

    let all_neg = vec![-5i64, -3, -1, -2, -4];
    let (s2, i2, j2) = max_subarray(&all_neg);
    println!("All-negative: sum={s2}, subarray={:?}", &all_neg[i2..=j2]);
}
(* Kadane's Algorithm โ€” functional fold, O(n) *)

let max_subarray arr =
  let n = Array.length arr in
  if n = 0 then failwith "empty array"
  else begin
    (* State: (curr_sum, best_sum, curr_start, best_start, best_end) *)
    let init = (arr.(0), arr.(0), 0, 0, 0) in
    let (_, best, _, bs, be) =
      Array.fold_left (fun (curr, best, cs, bs, be) x ->
        let i = be + 1 in (* current index in fold โ€” we track via be *)
        let (curr', cs') =
          if x > curr + x then (x, i)
          else (curr + x, cs)
        in
        if curr' > best then (curr', curr', cs', cs', i)
        else (curr', best, cs', bs, be)
      ) init (Array.sub arr 1 (n - 1))
    in
    (* Simpler version with index tracking *)
    ignore (best, bs, be);

    (* Clean re-implementation with proper index tracking *)
    let best_sum   = ref arr.(0) in
    let best_start = ref 0 in
    let best_end   = ref 0 in
    let curr_sum   = ref arr.(0) in
    let curr_start = ref 0 in
    for i = 1 to n - 1 do
      if arr.(i) > !curr_sum + arr.(i) then begin
        curr_sum   := arr.(i);
        curr_start := i
      end else
        curr_sum := !curr_sum + arr.(i);
      if !curr_sum > !best_sum then begin
        best_sum   := !curr_sum;
        best_start := !curr_start;
        best_end   := i
      end
    done;
    (!best_sum, !best_start, !best_end)
  end

let () =
  let arr = [| -2; 1; -3; 4; -1; 2; 1; -5; 4 |] in
  let (sum, s, e) = max_subarray arr in
  Printf.printf "Array: ";
  Array.iter (fun x -> Printf.printf "%d " x) arr;
  print_newline ();
  Printf.printf "Max subarray sum: %d  (indices %d..%d)\n" sum s e;
  Printf.printf "Subarray: ";
  for i = s to e do Printf.printf "%d " arr.(i) done;
  print_newline ();

  let all_neg = [| -5; -3; -1; -2; -4 |] in
  let (s2,_,_) = max_subarray all_neg in
  Printf.printf "All-negative max sum: %d\n" s2