Maximum Subarray Sum

Context: FIT2004_MOC · the applied sheet’s cleanest upgrade — and the vault’s home for prefix sums, which several other DPs depend on Parent Framework: Dynamic Programming

Quick Revision

  • 🎯 Objective: find the contiguous non-empty of maximum sum ➔ by prefix sums over all intervals, by anchoring the subproblem at its right end.
  • 📦 Core Components: prefix sums ➔ any interval in | anchored ➔ “best sum ending at ” ➔ the greedy restart.
  • ⚡ Key Constraint: the subarray is non-empty, so an all-negative input answers with its largest single element, never . Initialising the running maximum to is the marked error.

📝 How It Works

1. Prefix Sums — the Solution (applied P6a)

  • The identity ➔ , so every interval sum is one subtraction once the prefixes are known.
  • The subproblem ➔ DP[i] = {the sum of the elements in a[1..i]} for :
  • The answer ➔ ; the strict is what keeps the subarray non-empty.
  • Cost ➔ to build the table intervals at each . Naively this is — recomputing each interval sum by a scan — so the prefix table alone removes a whole factor of .
  • Reuse this table everywhere ➔ the ferry problem needs in , and text justification needs the running line length; both precompute prefix sums exactly like this ➔ Dynamic Programming §5.

2. Anchoring the Right End — the Solution (applied P6b)

  • The greedy observation ➔ looking at index , if the best sum finishing at is positive it is worth extending; if it is it can only hurt, so restart at .
  • The subproblem must force in ➔ DP'[j] = {the maximum sum of a subarray ENDING at position j}, i.e. — is a member by construction.
  • The answer is a scan ➔ , not — the anchor forces it, exactly as in LIS.
  • Cost ➔ subproblems at time.
  • auxiliary space ➔ each subproblem reads only its predecessor, so keep prev and best in two variables and never build the array. That space-saving trick applies to any DP whose recurrence looks back a constant distance.
  • Where the greed lives ➔ this is DP with a greedy element bolted on: the restart decision is made locally and never revisited, which is legal here because a non-positive prefix provably cannot help any later subarray ➔ Greedy Algorithm §2.

3. The 2-D Generalisation — Maximum Sum Submatrix (applied P14, [D])

  • by 2-D prefix sums ➔ DP[i][j] = {the sum of the submatrix A[1..i][1..j]}, built by inclusion–exclusion:
  • Why the subtraction ➔ the two rectangles and overlap in , which would otherwise be counted twice. Draw the four rectangles — the picture is the derivation.
  • Any submatrix in ➔ ; there are submatrices ⟹ .
  • by reduction to the 1-D case ➔ a maximum-sum submatrix starts at some row and has some height . Fix both, compress rows into a single row of column sums, and run §2’s algorithm on it.
  • The compression is incremental ➔ growing the height by one costs (add the next row to the previous compressed row), so the whole sweep is starting rows heights scan .
  • The pattern generalises ➔ the -D problem compresses to -D, giving : in 1-D, in 2-D, in 3-D.

⚙️ Core Implementation

🔹 , auxiliary, with the index range

🔭 Beyond the lecture (not in the slides): §2 is commonly called Kadane's algorithm; the applied sheet derives it from scratch and does not name it, so derive it rather than citing it.

⚖️ Complexity

ApproachTimeAuxiliary spaceAnswer location
Naive all-intervals, sum by scan—
Prefix sums all intervals (P6a),
Anchored right end (P6b) — scan
Submatrix, 2-D prefix sums (P14.1) over all rectangles
Submatrix, row compression 1-D (P14.2) over start row height
-D generalisationrecursive compression
  • All cases coincide ➔ every version has fixed loop bounds and no early exit, so best average worst.

📊 Exam Execution Trace & Applied Exercises

Manual Execution Trace

.

?Rule firedbest so far
norestart
norestart
yesextend
norestart
yesextend
yesextend
yesextend
yesextend
yesextend

Final Extracted Output: maximum sum , achieved by (the restart at fixes the left end).

  • Read the trap ➔ : the anchored subproblem means the last cell is not the answer. The running best is mandatory.
  • Read the left endpoint ➔ it is the position of the most recent restart, which is why cur_lo is updated only in the else branch.
  • Read the negative case ➔ on every step restarts, and the answer is — the largest single element, exactly as the spec demands.

Applied Exercise — from to in one sentence (P6)

Problem: name the change of subproblem that removes the factor of , and justify the greedy step.

Final Extracted Output: time, auxiliary. The reusable move (LO1): when the answer is indexed by a pair, try re-defining the subproblem so one of the two indices is implied — here, by forcing to be the last element.

⚠️ Common Mistakes

  • 💡 Initialising the maximum to ➔ silently allows the empty subarray and returns on all-negative input.
  • 💡 Answer ➔ anchored subproblem ⟹ scan. Same failure mode as LIS.
  • 💡 Quoting space for the fast version ➔ the recurrence looks back one cell, so it is auxiliary; quoting the array you did not need is a lost mark.
  • 💡 Forgetting the term in 2-D ➔ inclusion–exclusion has four terms; three of them is the classic off-by-a-rectangle.
  • 💡 Calling the submatrix method “just Kadane in 2-D” ➔ the work is the row compression (and its incremental update); the 1-D routine is the black box being reused.

🧠 Active Recall