Pattern family: Dynamic Programming / Array Optimization Difficulty range: Easy β Hard Language: Java Problems file: 02_kadane_pattern_problems.md
- How to identify this pattern β start here
- What is this pattern?
- Core rules
- 2-Question framework
- Variants table
- Universal Java template
- Quick reference cheatsheet
- Common mistakes
- Complexity summary
| 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: 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.
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 ondiff= 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.
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
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.
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);
}| 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 |
| 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
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 |
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
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
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
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
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
| # | 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) |
// βββ 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;
}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]
// 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);
}// 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.// 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);// 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]);// 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// 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| 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
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