Skip to content

Latest commit

Β 

History

History
609 lines (484 loc) Β· 21.7 KB

File metadata and controls

609 lines (484 loc) Β· 21.7 KB

Kadane's Algorithm β€” Notes & Templates

Pattern family: Dynamic Programming / Array Optimization Difficulty range: Easy β†’ Hard Language: Java Problems file: 02_kadane_pattern_problems.md


Table of Contents

  1. How to identify this pattern ← start here
  2. What is this pattern?
  3. Core rules
  4. 2-Question framework
  5. Variants table
  6. Universal Java template
  7. Quick reference cheatsheet
  8. Common mistakes
  9. Complexity summary

1. How to identify this pattern

Keyword scanner

If you see this... It means
"maximum subarray sum" classic Kadane
"minimum subarray sum" Kadane with negation
"maximum product subarray" Kadane tracking both max and min
"contiguous subarray" + "maximum / minimum" Kadane candidate
"circular array" + "max subarray" total sum βˆ’ min subarray
"best time to buy and sell stock" Kadane on price differences
"maximum sum after one deletion" Kadane with DP state
"house robber / non-adjacent" Kadane-style DP
"maximum sum rectangle in matrix" 2D Kadane
"max sum submatrix ≀ k" 2D Kadane + binary search
"maximum alternating subarray sum" Kadane with sign flip

Brute force test

Brute force: check every subarray β†’ O(nΒ²) or O(nΒ³)
β†’ If you want max/min of a contiguous subarray SUM or PRODUCT
  AND the problem has "optimal substructure" (best ending here depends only on best ending at i-1)
β†’ Kadane = O(n)

Key insight: at each position i, you have exactly 2 choices:
  1. Extend the previous subarray: currentSum + arr[i]
  2. Start fresh from here:        arr[i]
Take the max of the two. That's Kadane.

The 5 confirming signals

Signal 1 β€” "Maximum / minimum sum of contiguous subarray"

This is the textbook Kadane problem. Direct application.

Signal 2 β€” "At each step, extend or restart"

If adding the current element to the previous result could make it worse, you should restart. This is the Kadane decision at every step.

Signal 3 β€” "Circular array variant"

Two cases: max subarray doesn't wrap (normal Kadane) OR it wraps (total βˆ’ min subarray). Take the max of both.

Signal 4 β€” "Buy/sell stock" or "profit/loss sequence"

Convert prices to daily differences: diff[i] = price[i] - price[i-1]. Then max subarray sum on diff = max profit. Kadane in disguise.

Signal 5 β€” "Matrix / 2D" + "max sum rectangle"

Fix left and right column boundaries, reduce each row to a 1D array by summing column-wise, run Kadane on that 1D array.

Decision flowchart

Problem involves contiguous subarray/substring?
        β”‚
        β”œβ”€β”€ YES β†’ Is it sum-based?
        β”‚              β”œβ”€β”€ Max sum          β†’ Classic Kadane (Template A)
        β”‚              β”œβ”€β”€ Min sum          β†’ Kadane with min (Template A, track min)
        β”‚              β”œβ”€β”€ Max sum circular β†’ Kadane + (total βˆ’ min subarray) (Template C)
        β”‚              β”œβ”€β”€ Max sum + 1 del  β†’ Kadane with forward/backward prefix (Template D)
        β”‚              └── Max product      β†’ Kadane tracking both max and min (Template B)
        β”‚
        β”œβ”€β”€ YES β†’ Is it 2D (matrix)?
        β”‚              └── Max sum rectangle β†’ fix columns, reduce to 1D, run Kadane (Template E)
        β”‚
        β”œβ”€β”€ YES β†’ Is it a sequence with choices (rob/skip)?
        β”‚              └── House Robber style β†’ Kadane-style DP (Template F)
        β”‚
        └── NO β†’ Not Kadane

2. What is this pattern?

Kadane's algorithm answers: "What is the maximum sum subarray ending at position i?"

At every index, you make one decision:

maxEndingHere = max(arr[i], maxEndingHere + arr[i])

Either start fresh at arr[i], or extend the previous best subarray.

Why it works β€” optimal substructure: The best subarray ending at i either:

  • Starts at i (start fresh when previous sum is negative)
  • Extends the best subarray ending at i-1

This means you only need to remember one value from the previous step β†’ O(n) time, O(1) space.

arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

maxEndingHere: -2 β†’ 1 β†’ -2 β†’ 4 β†’ 3 β†’ 5 β†’ 6 β†’ 1 β†’ 5
maxSoFar:      -2    1    1    4    4    5    6    6    6

Answer: 6  (subarray [4,-1,2,1])

The core insight β€” negative prefix kills you: If maxEndingHere < 0, adding more elements can only decrease future sums. So restart from the next element.


3. Core rules

Rule 1 β€” Kadane makes exactly one decision per element

// At each index, choose: restart OR extend
maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);

// Alternative equivalent form:
if (maxEndingHere < 0) maxEndingHere = 0;  // restart implicitly
maxEndingHere += arr[i];
maxSoFar = Math.max(maxSoFar, maxEndingHere);

Both forms are correct. The first is more explicit (handles all-negative arrays correctly).

Rule 2 β€” All-negative array: answer is the least negative element

// WRONG β€” returns 0 for all-negative arrays (empty subarray)
int maxSum = 0;
for (int n : arr) {
    maxSum = Math.max(maxSum, maxSum + n);  // never goes below 0
}
// For [-3,-1,-2], returns 0 β€” but problem usually asks for subarray (non-empty)

// CORRECT β€” initialise maxSoFar to first element
int maxEndingHere = arr[0], maxSoFar = arr[0];
for (int i = 1; i < arr.length; i++) {
    maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
    maxSoFar = Math.max(maxSoFar, maxEndingHere);
}

Rule 3 β€” For product subarrays, track both max AND min

// Products: negative Γ— negative = positive
// So the minimum so far can become the maximum when multiplied by a negative number
int maxProd = arr[0], minProd = arr[0], result = arr[0];
for (int i = 1; i < arr.length; i++) {
    if (arr[i] < 0) {
        int temp = maxProd;
        maxProd = minProd;
        minProd = temp;   // swap: multiplying by negative flips max/min
    }
    maxProd = Math.max(arr[i], maxProd * arr[i]);
    minProd = Math.min(arr[i], minProd * arr[i]);
    result = Math.max(result, maxProd);
}

4. 2-Question framework

Question 1 β€” What are you optimising?

Optimise Approach
Maximum sum Classic Kadane β€” track maxEndingHere
Minimum sum Kadane with min instead of max
Maximum product Kadane tracking both maxProd and minProd
Maximum sum circular max(Kadane, total βˆ’ minKadane)
Maximum sum with 1 deletion Forward + backward prefix arrays
Maximum sum 2D rectangle Fix columns + 1D Kadane

Question 2 β€” Are there constraints or modifications?

Constraint Modification
Array is circular Use total βˆ’ minSubarray for wrapping case
Can delete exactly one element Precompute forward and backward Kadane arrays
Non-adjacent elements (House Robber) dp[i] = max(dp[i-1], dp[i-2] + arr[i])
2D matrix Fix left/right column boundary, column-compress to 1D, run Kadane
Sum ≀ k constraint 2D Kadane + sorted set binary search

Decision shortcut:

  • "max/min subarray sum" β†’ classic Kadane
  • "max product" β†’ track both max and min products
  • "circular" β†’ total sum βˆ’ min subarray
  • "one deletion allowed" β†’ forward + backward prefix Kadane
  • "2D matrix" β†’ column compression + 1D Kadane
  • "non-adjacent" β†’ house robber DP

5. Variants table

Common core: at each step, decide to extend or restart
What differs: what you track and the restart condition

Variant Track Restart condition Special logic
Max sum maxEndingHere previous sum < 0 none
Min sum minEndingHere previous sum > 0 negate or use min
Max product maxProd, minProd zero resets both swap on negative
Circular max sum both max and min β€” total βˆ’ minSubarray
Max sum with 1 deletion forward[], backward[] β€” forward[i-1] + backward[i+1]
Max alternating sum maxEnd, minEnd β€” toggle sign each step
House Robber dp[i-1], dp[i-2] β€” non-adjacent constraint
2D max rectangle 1D Kadane per column pair β€” O(nΒ²m) overall

5b. Pattern categories β€” all types covered

Category 1 β€” Classic Kadane (Sum)

Single decision at each step: extend or restart.

int cur = arr[0], best = arr[0];
for (int i = 1; i < n; i++) {
    cur = Math.max(arr[i], cur + arr[i]);
    best = Math.max(best, cur);
}

Problems: Maximum Subarray Sum, Minimum Subarray Sum, Longest Positive Sum Subarray


Category 2 β€” Product Kadane

Multiplying by negative flips max to min. Track both.

int maxP = arr[0], minP = arr[0], best = arr[0];
for (int i = 1; i < n; i++) {
    if (arr[i] < 0) { int t = maxP; maxP = minP; minP = t; }
    maxP = Math.max(arr[i], maxP * arr[i]);
    minP = Math.min(arr[i], minP * arr[i]);
    best = Math.max(best, maxP);
}

Problems: Maximum Product Subarray, Maximum Absolute Sum


Category 3 β€” Circular Kadane

Two cases: subarray doesn't wrap (normal Kadane) OR wraps (use total βˆ’ min).

int maxKadane = kadane_max(arr);
int minKadane = kadane_min(arr);
int total = sum(arr);
// If all negative: minKadane == total β†’ circular result would be 0 (empty) β†’ use maxKadane
int circularMax = (total == minKadane) ? maxKadane : Math.max(maxKadane, total - minKadane);

Problems: Maximum Sum Circular Subarray


Category 4 β€” Kadane with State (DP)

More than one state tracked per position.

// One deletion: precompute forward and backward
int[] fwd = new int[n];  // max subarray sum ending AT i
int[] bwd = new int[n];  // max subarray sum starting FROM i

// Answer for deleting index i: fwd[i-1] + bwd[i+1]

Problems: Maximum Subarray Sum with One Deletion, House Robber


Category 5 β€” 2D Kadane

Fix left and right column boundaries. Sum each row between those columns. Run 1D Kadane on the resulting column-sum array.

for (int left = 0; left < cols; left++) {
    int[] rowSum = new int[rows];
    for (int right = left; right < cols; right++) {
        for (int r = 0; r < rows; r++) rowSum[r] += matrix[r][right];
        maxSumSoFar = Math.max(maxSumSoFar, kadane(rowSum));
    }
}

Problems: Maximum Sum Rectangle in 2D Matrix, Max Sum Submatrix ≀ K


Summary β€” all 5 categories

# Category Core idea Time
1 Classic (sum) extend or restart O(n)
2 Product track max AND min O(n)
3 Circular total βˆ’ minKadane O(n)
4 With state / DP forward + backward arrays O(n)
5 2D matrix column compression + 1D Kadane O(nΒ²m)

6. Universal Java template

// ─── TEMPLATE A: Classic Kadane β€” Max Subarray Sum ───────────────────────────
public int maxSubarraySum(int[] arr) {
    int maxEndingHere = arr[0];  // best subarray sum ENDING at current index
    int maxSoFar = arr[0];       // global best answer

    for (int i = 1; i < arr.length; i++) {
        maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
        maxSoFar = Math.max(maxSoFar, maxEndingHere);
    }
    return maxSoFar;
}

// ─── TEMPLATE A2: Classic Kadane β€” Min Subarray Sum ──────────────────────────
public int minSubarraySum(int[] arr) {
    int minEndingHere = arr[0];
    int minSoFar = arr[0];

    for (int i = 1; i < arr.length; i++) {
        minEndingHere = Math.min(arr[i], minEndingHere + arr[i]);
        minSoFar = Math.min(minSoFar, minEndingHere);
    }
    return minSoFar;
}

// ─── TEMPLATE A3: Kadane with subarray indices (track start/end) ──────────────
public int[] maxSubarrayWithIndices(int[] arr) {
    int maxEndingHere = arr[0], maxSoFar = arr[0];
    int start = 0, end = 0, tempStart = 0;

    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > maxEndingHere + arr[i]) {
            maxEndingHere = arr[i];
            tempStart = i;                       // potential new start
        } else {
            maxEndingHere += arr[i];
        }
        if (maxEndingHere > maxSoFar) {
            maxSoFar = maxEndingHere;
            start = tempStart;
            end = i;
        }
    }
    return new int[]{maxSoFar, start, end};
}

// ─── TEMPLATE B: Product Kadane ───────────────────────────────────────────────
public int maxProductSubarray(int[] arr) {
    int maxProd = arr[0], minProd = arr[0], result = arr[0];

    for (int i = 1; i < arr.length; i++) {
        if (arr[i] < 0) {
            int temp = maxProd;      // swap: negative flips max ↔ min
            maxProd = minProd;
            minProd = temp;
        }
        maxProd = Math.max(arr[i], maxProd * arr[i]);  // extend or restart
        minProd = Math.min(arr[i], minProd * arr[i]);
        result = Math.max(result, maxProd);
    }
    return result;
}

// ─── TEMPLATE C: Circular Kadane ─────────────────────────────────────────────
public int maxCircularSubarraySum(int[] arr) {
    int maxKadane = kadaneMax(arr);
    int minKadane = kadaneMin(arr);
    int total = 0;
    for (int n : arr) total += n;

    // If all negative: total == minKadane β†’ wrapping gives 0 (empty subarray) β†’ invalid
    return (total == minKadane) ? maxKadane : Math.max(maxKadane, total - minKadane);
}

// ─── TEMPLATE D: Kadane with Forward/Backward arrays (one deletion) ───────────
public int maxSumAfterOneDeletion(int[] arr) {
    int n = arr.length;
    int[] fwd = new int[n];   // fwd[i] = max subarray sum ENDING at i
    int[] bwd = new int[n];   // bwd[i] = max subarray sum STARTING at i

    fwd[0] = arr[0];
    for (int i = 1; i < n; i++)
        fwd[i] = Math.max(arr[i], fwd[i-1] + arr[i]);

    bwd[n-1] = arr[n-1];
    for (int i = n-2; i >= 0; i--)
        bwd[i] = Math.max(arr[i], bwd[i+1] + arr[i]);

    int result = Arrays.stream(arr).max().getAsInt();  // single element (delete everything else)
    for (int i = 1; i < n-1; i++)
        result = Math.max(result, fwd[i-1] + bwd[i+1]);  // delete index i

    return result;
}

// ─── TEMPLATE E: 2D Kadane β€” Maximum Sum Rectangle ───────────────────────────
public int maxSumRectangle(int[][] matrix) {
    int rows = matrix.length, cols = matrix[0].length;
    int maxSum = Integer.MIN_VALUE;

    for (int left = 0; left < cols; left++) {
        int[] rowSum = new int[rows];            // compressed row sums

        for (int right = left; right < cols; right++) {
            for (int r = 0; r < rows; r++)
                rowSum[r] += matrix[r][right];   // add column `right` to compression

            maxSum = Math.max(maxSum, kadaneMax(rowSum));  // 1D Kadane on compressed array
        }
    }
    return maxSum;
}

// ─── TEMPLATE F: House Robber (Kadane-style DP, non-adjacent) ─────────────────
public int houseRobber(int[] nums) {
    if (nums.length == 1) return nums[0];
    int prev2 = nums[0];
    int prev1 = Math.max(nums[0], nums[1]);

    for (int i = 2; i < nums.length; i++) {
        int curr = Math.max(prev1, prev2 + nums[i]);
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

7. Quick reference cheatsheet

SIGNAL IN PROBLEM                         β†’ VARIANT              β†’ KEY OPERATION
────────────────────────────────────────────────────────────────────────────────────
"maximum subarray sum"                    β†’ Classic Kadane       β†’ max(arr[i], cur + arr[i])
"minimum subarray sum"                    β†’ Min Kadane           β†’ min(arr[i], cur + arr[i])
"maximum product subarray"                β†’ Product Kadane       β†’ track maxProd + minProd, swap on negative
"maximum sum circular subarray"           β†’ Circular Kadane      β†’ max(maxKadane, total - minKadane)
"max sum with one deletion"               β†’ Forward + Backward   β†’ fwd[i-1] + bwd[i+1]
"maximum sum rectangle in matrix"         β†’ 2D Kadane            β†’ fix columns, compress rows, 1D Kadane
"best time to buy sell stock"             β†’ Kadane on diff array β†’ profit[i] = price[i] - price[i-1]
"house robber / non-adjacent"             β†’ DP Kadane            β†’ max(prev1, prev2 + curr)
"maximum alternating subarray sum"        β†’ Alternating Kadane   β†’ toggle +/- each step
"max absolute sum of any subarray"        β†’ Kadane dual          β†’ max(maxKadane, -minKadane)

The one decision Kadane makes at every step:

cur = Math.max(arr[i], cur + arr[i]);
//             ^restart   ^extend
// Restart when prev sum is negative (it only hurts us)

Key formulas:

Max sum            = Kadane(arr)
Min sum            = -Kadane(-arr)  OR  Kadane_min(arr)
Circular max       = max(Kadane_max, total - Kadane_min)
Max absolute sum   = max(Kadane_max, abs(Kadane_min))
Max profit (stock) = Kadane(diff[]) where diff[i] = price[i] - price[i-1]

8. Common mistakes

Mistake 1 β€” Initialising maxSoFar to 0 (breaks all-negative arrays)

// BUG β€” returns 0 for [-3,-1,-2] but answer is -1
int maxSum = 0;
for (int n : arr) maxSum = Math.max(maxSum, maxSum + n);

// FIX β€” initialise to arr[0], start loop from index 1
int cur = arr[0], best = arr[0];
for (int i = 1; i < arr.length; i++) {
    cur = Math.max(arr[i], cur + arr[i]);
    best = Math.max(best, cur);
}

Mistake 2 β€” Not handling zero in product Kadane

// BUG β€” zero in array: maxProd should reset, but swap logic breaks
// CORRECT: Math.max(arr[i], maxProd * arr[i]) handles zero because:
// if arr[i] == 0: max(0, anything*0=0) = 0 β†’ correctly resets
// No special case needed for zero.

Mistake 3 β€” Circular Kadane edge case (all negative)

// BUG β€” if all elements negative, total == minKadane
// total - minKadane = 0 β†’ but empty subarray is not valid
int circular = total - minKadane;
return Math.max(maxKadane, circular);  // WRONG for all-negative

// FIX β€” check if entire array is selected as minimum
if (total == minKadane) return maxKadane;  // all negative: can't wrap
return Math.max(maxKadane, total - minKadane);

Mistake 4 β€” Not swapping in product Kadane before updating

// BUG β€” swap AFTER update causes wrong result
maxProd = Math.max(arr[i], maxProd * arr[i]);
minProd = Math.min(arr[i], minProd * arr[i]);
if (arr[i] < 0) { int t = maxProd; maxProd = minProd; minProd = t; }  // WRONG: too late

// FIX β€” swap BEFORE computing new max/min
if (arr[i] < 0) { int t = maxProd; maxProd = minProd; minProd = t; }
maxProd = Math.max(arr[i], maxProd * arr[i]);
minProd = Math.min(arr[i], minProd * arr[i]);

Mistake 5 β€” Wrong edge case in one-deletion Kadane

// BUG β€” deleting first or last element not handled
for (int i = 1; i < n-1; i++)
    result = Math.max(result, fwd[i-1] + bwd[i+1]);
// Deleting index 0 β†’ answer = sum of arr[1..n-1] starting fresh
// Deleting index n-1 β†’ answer = fwd[n-2]

// FIX β€” also consider:
result = Math.max(result, bwd[1]);     // delete index 0
result = Math.max(result, fwd[n-2]);   // delete index n-1

Mistake 6 β€” House Robber wrong initialisation for 2 elements

// BUG
int prev2 = nums[0], prev1 = nums[1];  // if n=2, skips max(nums[0], nums[1])

// FIX
int prev2 = nums[0];
int prev1 = Math.max(nums[0], nums[1]);  // for 2 elements, take the larger one

9. Complexity summary

Problem Time Space Notes
Maximum Subarray Sum O(n) O(1) classic Kadane
Minimum Subarray Sum O(n) O(1) min variant
Maximum Product Subarray O(n) O(1) track max and min
Maximum Sum Circular Subarray O(n) O(1) two Kadane passes
Longest Positive Sum Subarray O(n) O(1) prefix sum variant
Max Subarray Sum with One Deletion O(n) O(n) fwd + bwd arrays
Max Absolute Sum of Any Subarray O(n) O(1) max(maxKadane, -minKadane)
Best Time to Buy and Sell Stock O(n) O(1) Kadane on diff array
House Robber O(n) O(1) two variables DP
Maximum Alternating Subarray Sum O(n) O(1) toggle sign state
Max Sum Rectangle in 2D Matrix O(nΒ²m) O(n) n=cols, m=rows
Max Sum Submatrix ≀ K O(nΒ²m log m) O(m) + sorted set binary search

General rules:

  • 1D Kadane β†’ always O(n) time, O(1) space
  • 2D Kadane β†’ O(nΒ²m) β€” n column pairs Γ— m rows per Kadane run
  • Variants with extra arrays (one deletion) β†’ O(n) space
  • Product Kadane handles zeros and negatives in the same O(n) pass

Solve order for interviews

Level 1 β€” Must do before any interview:
  LC 53  β†’ Classic Kadane (foundation)
  LC 152 β†’ Product Kadane (product variant)
  LC 918 β†’ Circular Kadane (circular variant)
  LC 121 β†’ Stock (Kadane in disguise)

Level 2 β€” Product-based company must:
  LC 1186 β†’ One deletion (DP state)
  LC 198  β†’ House Robber (non-adjacent DP)
  LC 363  β†’ 2D Kadane (matrix variant)

Level 3 β€” Google / Hard:
  LC 363(K) β†’ 2D + binary search
  LC 2036   β†’ Alternating subarray sum