Maximum Subarray problem in DSA: Brute Force to Kadane’s Algorithm

  • Last Updated: July 13, 2026
  • By: javahandson
  • Series
img

Maximum Subarray problem in DSA: Brute Force to Kadane’s Algorithm

Learn the Maximum Subarray problem in DSA with a clear walkthrough: brute force, Kadane’s Algorithm, divide and conquer, dry runs, and a full complexity comparison. As a developer I will be using Java code but pseudo-codes, explanation and dry runs are language neutral and you can understand the concept easily. So don’t skip it because language is just a syntax.

1. Introduction

The Maximum Subarray problem in DSA is a small question with a big payoff. It looks tricky at first glance. Once you meet Kadane’s Algorithm, though, the whole thing clicks, and you carry that trick into many harder problems.

Here is the task in plain words. Somebody hands you an array of numbers, and some of them may be negative. You must find the contiguous slice with the largest sum. The slice has to be one unbroken run, so skipping around is not allowed.

In our last article we solved Contains Duplicate by remembering which values we had already seen. Now the focus shifts from membership to a running total, and that shift opens up a different family of problems.

We build up in three stages. Brute force comes first, because a correct slow answer beats a clever wrong one. Kadane’s one-pass trick comes next, arriving in two steps so the leap feels small. Divide and conquer closes the set, since interviewers love asking for it as a follow-up.

1.1 What This Article Covers

Here is the road map for the rest of the guide.

  • A plain statement of the problem, plus the constraint beginners read straight past.
  • Three small ideas, a running sum, a best-so-far, and one extend-or-restart decision.
  • Brute force, with pseudocode, code, a dry run, and its real cost.
  • Kadane’s Algorithm built in two steps, from an obvious reset to the classic one-liner.
  • Divide and conquer, the follow-up that catches people off guard.
  • Two more follow-ups, the mistakes that cost people offers, and a small program that ties it together.
  • Ten interview questions with short, quotable answers.

2. Understanding the Problem

Let us pin down the rules first. A clear problem statement keeps you out of trouble later, and it costs about thirty seconds.

2.1 The Rules in Plain Words

Every version of this question comes down to the same handful of rules.

  • You receive an array of integers, for example [-2, 1, -3, 4, -1, 2, 1].
  • Your job is to return the largest sum of any contiguous subarray.
  • The slice must hold at least one element, so an empty slice is never the answer.
  • Values may be negative, positive, or zero, which is exactly what makes this interesting.
  • The classic version asks for the sum only, not the slice itself.

Worth knowing about the original version of this question: the array can hold up to about 100,000 numbers, and each value sits between minus 10,000 and plus 10,000. Multiply those out and the largest possible total is around one billion, which still fits comfortably in a 32-bit integer. So overflow is not a worry here, though it would be if the limits were looser.

2.2 A First Example

Take [-2, 1, -3, 4, -1, 2, 1]. The answer is 6, and it comes from the slice [4, -1, 2, 1].

Look at that winning slice for a second. It contains a negative number. Most beginners expect the best run to hold only positive values, yet the -1 earns its place because the 2 and the 1 sitting behind it more than pay for it.

That is the whole tension in this problem. A negative number is worth carrying when what follows makes up for it, and worth dropping when it does not. Every approach below is really just a different way of answering that one question.

2.3 Contiguous, Not Scattered

One word in the problem statement does a lot of heavy lifting: contiguous. The slice must be an unbroken run of neighbours.

Suppose you could skip elements freely. Then the answer would be trivial, because you would simply add up every positive number and ignore the rest. On our example that shortcut gives 1 plus 4 plus 2 plus 1, which is 8, and 8 is not the answer.

Getting 8 instead of 6 is the classic sign that somebody solved the subsequence problem by mistake. Read the word contiguous carefully, and say it back to your interviewer to confirm.

💡 Interview Insight
Interviewers often ask why an all-negative array like [-3, -1, -2] still has an answer. Because a subarray needs at least one element, the best you can do is the single largest value, which is -1. Naming this edge case early shows you read the constraints carefully.

3. Concepts You Need Here

Three small ideas carry this whole problem. All of them are easy, and all of them show up again in later array questions.

3.1 A Running Sum

A running sum is just a total you keep updating as you walk the array. Add each new value, and the total reflects everything in the current run.

Think of a shop till through the day. Sales push it up, refunds pull it down, and at any moment the number in front of you sums everything so far. You never re-add the earlier receipts.

Because the total already holds the past, extending a run costs one addition. That cheapness is what lets a single pass replace a nest of loops.

3.2 A Best-So-Far

The second idea is even simpler. While the running sum climbs and dips, a separate variable quietly remembers the highest total you have ever reached.

Picture a high-water mark on a harbour wall. The tide rises and falls all day, and the mark stays put at the highest point. Nothing that happens afterwards can lower it.

So you need two numbers, not one. Merge them and you lose the record the moment the current run turns bad, which is the single most common bug in this problem.

3.3 Extend or Restart

Now the idea that makes everything fast. Standing on any number, you face exactly one decision.

  • Extend the run you are already building by adding this number to it.
  • Restart from this number alone, throwing the old run away.
  • Pick whichever of those two gives the larger total.
  • Compare the winner with the high-water mark, then step forward.

Why is that safe? Because a run ending at the current position either includes the previous run or it does not. Those are the only two shapes available, so checking both covers everything.

Here is the rule of thumb underneath it. Carrying a negative total forward can only shrink whatever comes next, so you drop it. Carrying a positive total forward can only help, so you keep it.

4. Approach 1: Brute Force

The first idea most people reach for is direct. Try every possible slice, add each one up, and keep the largest total you find.

4.1 Pseudocode

best = -infinity
for start from 0 to n-1:
    sum = 0
    for end from start to n-1:
        sum = sum + nums[end]
        best = max(best, sum)
return best

The outer loop fixes a starting point. The inner loop stretches the slice one element at a time and grows the running sum. After each step we compare that sum against our best.

4.2 Java Code

public class MaximumSubarrayBrute {
    public static int maxSubArray(int[] nums) {
        int best = Integer.MIN_VALUE;
        for (int start = 0; start < nums.length; start++) {
            int sum = 0;
            for (int end = start; end < nums.length; end++) {
                sum += nums[end];
                best = Math.max(best, sum);
            }
        }
        return best;
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // Output: 6
    }
}

4.3 Why This Works, Step by Step

Let us go slow and picture what the loops are really doing. A subarray is just a slice with a left edge and a right edge. Brute force says: try every left edge, and for each one, try every right edge. That covers all possible slices.

Think of it with your hands. Put your left finger on one number. Then drag your right finger across the array, adding up values as you go. Every time the right finger moves, you have a new slice, so you check its total.

Now let us tie each part of the loop to that picture.

  • The outer index is your left finger. It sits on one number and stays there while the inner loop runs, then steps one place right.
  • Zeroing the running total happens every time the left finger moves. This matters. Each new left edge begins a brand new slice, so the old total must go.
  • The inner index is your right finger. It begins on the same spot as the left finger, then walks rightward to the end of the array.
  • Adding the value under the right finger grows the total. Since nothing resets it inside the inner loop, it always holds the sum of the whole current slice.
  • Comparing that total against the record updates the record whenever this slice wins. Otherwise the record stays put.
  • Once both loops finish, every slice has had its turn, and the record holds the winner.

4.4 Why the Cost Is Not n Cubed

Here is the detail that trips people up. We never rebuild a sum from scratch.

As the right finger moves one step, we add exactly one more number to the total we already had. A naive version would loop over the whole slice again to total it up, which adds a third layer of work.

That small reuse is worth a lot. It saves an enormous amount of repeated addition, and it is the reason the cost lands at n squared rather than n cubed.

This version is easy to trust, because it literally checks everything. Nothing gets skipped, so it cannot miss the answer. Its only weakness is speed, which we fix next.

4.5 Dry Run of the Brute Force

Trace a short array, nums = [1, -3, 4], and follow the slices in the order the loops produce them.

Left edge Right edge Slice Sum Best so far
0 0 [1] 1 1
0 1 [1, -3] -2 1
0 2 [1, -3, 4] 2 2
1 1 [-3] -3 2
1 2 [-3, 4] 1 2
2 2 [4] 4 4

Six slices for three numbers, and the winner turns out to be the very last one. The single element [4] beats every longer slice, because dragging the -3 along always costs more than it returns.

Notice the shape of the work too. The left edge at 0 produced three slices, the next produced two, and the last produced one. Add those up and you get 6, which is the familiar half-of-n-squared pattern.

4.6 Time and Space Cost

Let us count the work in plain terms. The left edge can sit on any of the n numbers. For each of those spots, the right edge walks across the rest of the array, which is again up to n steps. So the total work grows like n times n.

In Big-O terms that is O(n squared) time. At 10 numbers you get around 50 steps, which is nothing. At 100,000 numbers you get about five billion, and the program crawls.

Space is much friendlier. Two extra variables cover everything, no matter how large the array grows, so the space cost is O(1). For small arrays this approach is perfectly fine. For large ones, we need something faster.

5. Approach 2: Kadane’s Algorithm

Now for the version interviewers really want. Kadane’s Algorithm walks the array just once. Instead of rebuilding every slice, it carries one running total and makes a smart choice at each number. To really get it, let us build up to the final code in two steps.

5.1 The Core Idea

Imagine walking along the array, adding numbers to a running total as you go. At some point that total might dip below zero. When it does, dragging it forward only hurts you. A negative total can never help a future slice, so you may as well throw it away and start again.

Here is a money analogy that makes it click. Say you save some money each day, but some days you lose money instead. Should your savings ever go negative, it is smarter to reset to zero and start saving fresh than to carry that debt forward. Kadane’s does exactly this with array sums.

So the plan has two moving parts. We keep a running total for the slice we are building, and we reset it to zero the moment it turns negative. Alongside that, a separate best value remembers the highest total we have ever reached.

5.2 First Version: Reset When the Total Goes Negative

Let us write that plan out literally, with a plain check for the reset. This version matches the analogy word for word, so it is the easiest one to read.

currentSum = 0
bestMax = nums[0]
for i from 0 to n-1:
    currentSum = currentSum + nums[i]     // add the current number
    bestMax = max(bestMax, currentSum)    // record before resetting
    if currentSum < 0:                    // total turned negative
        currentSum = 0                    // throw it away, start fresh
return bestMax
public class MaximumSubarrayReset {
    public static int maxSubArray(int[] nums) {
        int currentSum = 0;
        int bestMax = nums[0];
        for (int i = 0; i < nums.length; i++) {
            currentSum += nums[i];         // add the current number
            bestMax = Math.max(bestMax, currentSum);
            if (currentSum < 0) {          // total turned negative
                currentSum = 0;            // throw it away, start fresh
            }
        }
        return bestMax;
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // Output: 6
    }
}

Read it slowly and it mirrors the savings story exactly.

  • The running total holds the sum of the slice we are building. It starts at 0, like an empty piggy bank.
  • Our best value remembers the highest total we have seen. Seeding it with the first element keeps all-negative arrays correct, and we explain why below.
  • Each step adds the current number to the running total.
  • Recording happens next, so a single positive spike is never missed.
  • The reset fires last, so a total that drops below zero gets dropped and the next slice begins from zero.

Order matters enormously in those last two steps. Record the best first, then reset. Reset before recording and you can wipe out a total you never got credit for.

5.3 Dry Run of the Reset Version

Let us trace this version by hand on nums = [-2, 1, -3, 4, -1, 2, 1]. Watch the running total climb, then snap back to zero whenever it turns negative. We begin with the total at 0 and the record at -2.

i num Running total Record Negative? Reset?
0 -2 0 + -2 = -2 -2 Yes, reset to 0
1 1 0 + 1 = 1 1 No
2 -3 1 + -3 = -2 1 Yes, reset to 0
3 4 0 + 4 = 4 4 No
4 -1 4 + -1 = 3 4 No
5 2 3 + 2 = 5 5 No
6 1 5 + 1 = 6 6 No

Notice step 2. The total drops to -2, so we reset to zero right after recording. That reset is why step 3 starts clean at 4 instead of dragging the -2 forward. From there the run climbs to 6, which is our answer.

Step 4 deserves a glance as well. The total falls from 4 to 3, yet it stays positive, so no reset happens. Keeping that slightly bruised run alive is exactly what lets the 2 and the 1 push it up to 6. Reset too eagerly and you lose the winning slice.

5.4 Folding the Reset Into One Line

The reset version is clear, but Kadane’s classic form folds that check into a single line. Watch how the two ideas turn out to be the same thing.

In the reset version, a negative total gets thrown away, so the next number starts a run of just itself. A positive total survives, so the next number lands on top of it. That is exactly a choice between two values: the number alone, or the number plus the running total.

We can write that choice as one maximum. Should the running total be negative, adding it to the current number gives less than the number alone, so the maximum picks the number and effectively resets. Should the running total be positive, the sum wins, so the run extends. The check disappears into the comparison.

// The reset logic ...
currentSum += nums[i];
if (currentSum < 0) currentSum = 0;   // reset when negative

// ... becomes this single line:
currentMax = Math.max(nums[i], currentMax + nums[i]);

Both do the same job. The one-liner decides restart-or-extend in a single comparison, rather than adding first and cleaning up afterwards.

5.5 The Compact Version

currentMax = nums[0]
bestMax = nums[0]
for i from 1 to n-1:
    currentMax = max(nums[i], currentMax + nums[i])
    bestMax = max(bestMax, currentMax)
return bestMax
public class MaximumSubarrayKadane {
    public static int maxSubArray(int[] nums) {
        int currentMax = nums[0];
        int bestMax = nums[0];
        for (int i = 1; i < nums.length; i++) {
            currentMax = Math.max(nums[i], currentMax + nums[i]);
            bestMax = Math.max(bestMax, currentMax);
        }
        return bestMax;
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // Output: 6
    }
}

5.6 Reading the Compact Version Line by Line

Now let us walk through the final version slowly. Two values do all the work, and knowing their jobs first makes the code obvious.

  • The current run is the best total for a slice that ends right here, at the current number. It changes at every step.
  • The record is the best total seen anywhere in the array so far. It only moves when we beat it.

With those two roles clear, the lines make sense.

  • Both values start at the first element. That handles the opening number cleanly, so the array is never treated as empty, and a one-element array simply returns its only value.
  • The loop starts at position 1, not 0, since the first number is already baked into both values. Processing it twice would double-count it.
  • The maximum line is the heart of the whole algorithm. It weighs two choices: start fresh from this number alone, which is the reset in disguise, or extend the old run by adding this number. Whichever gives the larger total wins.
  • After the current run settles, we check it against the all-time record. A new high updates the record, and anything lower leaves it alone.
  • Once the pass finishes, the record holds the largest total any run ever reached, and that is our answer.

Notice why two separate values are essential. The current run can rise, fall, and even restart at a small number. Track only that and you lose the big total you hit earlier. The record acts like a photograph of your best moment, safe from whatever happens next.

5.7 Time and Space Cost

We touch each number exactly once and do a couple of comparisons per number. So the work grows in a straight line with the array size, which is O(n) time.

Memory never grows at all. Two integers cover any input length, so the space cost is O(1). That single clean pass is what makes Kadane’s Algorithm shine, and it beats brute force on both counts at the same time.

💡 Interview Insight
A common follow-up asks why we seed with the first element instead of 0. If you start the record at 0, an all-negative array wrongly returns 0, since no run can beat an empty total. Seeding with the first element keeps the answer correct for every input.

6. Dry Run of the Compact Version

Now let us trace the compact version on the same array, nums = [-2, 1, -3, 4, -1, 2, 1]. It should reach the same answer as the reset version, just through the maximum instead of a reset. We follow both the current run and the record as they change.

i num Current run = max(num, previous + num) Record
0 -2 -2 (start) -2
1 1 max(1, -2 + 1) = 1 1
2 -3 max(-3, 1 + -3) = -2 1
3 4 max(4, -2 + 4) = 4 4
4 -1 max(-1, 4 + -1) = 3 4
5 2 max(2, 3 + 2) = 5 5
6 1 max(1, 5 + 1) = 6 6

Look at step 3 closely. The running total had dipped to -2, so adding 4 gives only 2. Yet 4 by itself is larger, so the run restarts at 4. Compare that with the reset version, where the total had already snapped to 0, so 0 plus 4 also gave 4. Both land on the same value, which proves the two versions really are one idea in two costumes.

From there the run climbs to 6, which becomes our final answer.

6.1 The Same Dry Run on Paper

Maximum Subarray problem in DSA Kadane's Algorithm dry run

The sketch shows the same trace. The array sits on top, each step lists the choice, and the running values sit on the right. Many people find this picture easier than the table, so use whichever clicks for you.

6.2 A Run of All Negative Numbers

Now the case everybody gets wrong. Trace nums = [-3, -1, -2], where nothing positive exists anywhere.

i num Current run = max(num, previous + num) Record
0 -3 -3 (start) -3
1 -1 max(-1, -3 + -1) = -1 -1
2 -2 max(-2, -1 + -2) = -2 -1

The answer is -1, which is the single largest value in the array. That makes sense, because a slice must hold at least one element, so the least painful choice is the smallest loss on its own.

Watch what the maximum does here. Every step picks the bare number over the extended run, because adding a negative to a negative always makes things worse. So the algorithm restarts on every single step, and the record simply tracks the largest element.

7. Approach 3: Divide and Conquer

Interviewers who already know you can write Kadane’s often ask for this one next. It is slower, and that is not the point. The point is whether you can reason about a problem by splitting it in half.

7.1 The Core Idea

Cut the array down the middle. The best slice must now be one of exactly three things.

  • Entirely inside the left half, which the same routine can solve on a smaller input.
  • Entirely inside the right half, solved the same recursive way.
  • Straddling the middle, so it uses at least one element from each side.

Those three cases cover every possibility, so the answer is simply the largest of them. The first two come free from recursion, and only the third needs real work.

Handling the crossing case is easier than it sounds. Start at the middle and walk left, keeping a running total and remembering the best you reach. Then start just right of the middle and walk right the same way. Add those two bests together and you have the finest slice that spans the divide.

Why must you walk outward from the middle rather than scan freely? Because a crossing slice has no choice about its inner edges. It must touch the middle, so both halves grow outward from that fixed point.

7.2 Pseudocode

solve(nums, lo, hi):
    if lo == hi:
        return nums[lo]                  // one element, nothing to split
    mid = lo + (hi - lo) / 2
    left  = solve(nums, lo, mid)
    right = solve(nums, mid + 1, hi)
    return max(left, right, crossing(nums, lo, mid, hi))

crossing(nums, lo, mid, hi):
    sum = 0, bestLeft = -infinity
    for i from mid down to lo:            // walk outward, left of centre
        sum = sum + nums[i]
        bestLeft = max(bestLeft, sum)
    sum = 0, bestRight = -infinity
    for i from mid+1 up to hi:            // walk outward, right of centre
        sum = sum + nums[i]
        bestRight = max(bestRight, sum)
    return bestLeft + bestRight

7.3 Java Code

public class MaximumSubarrayDivide {

    public static int maxSubArray(int[] nums) {
        return solve(nums, 0, nums.length - 1);
    }

    private static int solve(int[] nums, int lo, int hi) {
        if (lo == hi) {
            return nums[lo];
        }
        int mid = lo + (hi - lo) / 2;
        int left = solve(nums, lo, mid);
        int right = solve(nums, mid + 1, hi);
        int cross = crossing(nums, lo, mid, hi);
        return Math.max(Math.max(left, right), cross);
    }

    private static int crossing(int[] nums, int lo, int mid, int hi) {
        int sum = 0;
        int bestLeft = Integer.MIN_VALUE;
        for (int i = mid; i >= lo; i--) {
            sum += nums[i];
            bestLeft = Math.max(bestLeft, sum);
        }
        sum = 0;
        int bestRight = Integer.MIN_VALUE;
        for (int i = mid + 1; i <= hi; i++) {
            sum += nums[i];
            bestRight = Math.max(bestRight, sum);
        }
        return bestLeft + bestRight;
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // Output: 6
    }
}

One detail is easy to miss. Both outward walks seed their best at negative infinity rather than zero. Seeding at zero would allow an empty half, and a crossing slice must take at least one element from each side.

7.4 Time and Space Cost

Each call splits its range in two and then scans the whole range once for the crossing case. Splitting gives about log n levels of recursion, and every level scans n elements in total. Multiply those and you land at O(n log n) time.

Space is O(log n), which is the depth of the recursion stack rather than any array we allocate. So this approach costs more time than Kadane’s and more memory too.

Learn it anyway. Interviewers ask for it because the split-into-three-cases habit unlocks a whole category of problems, and because they want to see whether you can reason about recursion under pressure.

8. Comparing the Three Approaches

All three give the correct answer. They simply charge different prices for it.

Point Brute Force Kadane’s Divide and Conquer
Time O(n squared) O(n) O(n log n)
Extra space O(1) O(1) O(log n)
Passes over the array Many One About log n
Recursion None None Yes
Work at 100,000 values About 5 billion steps 100,000 steps About 1.7 million steps
Ease of writing Very easy Easy once it clicks Fiddly
Good for interviews As a starting point As the final answer As the follow-up

Kadane’s wins on every measurable axis here, which is unusual. Normally a faster algorithm costs you memory, and this one does not.

So walk that path out loud in an interview. Mention brute force to show you understand the problem, move to Kadane’s to reach the best complexity, and keep divide and conquer in your pocket for the follow-up. That journey from slow to fast is the part interviewers actually grade.

💡 Interview Insight
If the interviewer asks you to also return the subarray itself, not just the sum, track the start and end positions alongside the current run. Reset the tentative start whenever the run restarts. Mentioning this extension out loud signals real depth.

9. Two Common Follow-Ups

Solve the base problem quickly and a follow-up usually arrives. These two show up most often, and both build on the single pass you already have.

9.1 Return the Subarray, Not Just the Sum

A bare number is a little abstract. Real tools want the actual slice. The fix costs three extra variables: a tentative start that moves whenever the run restarts, plus a start and end that freeze whenever the record improves.

public class MaximumSubarrayWithBounds {

    public static int[] bestSlice(int[] nums) {
        int currentMax = nums[0];
        int bestMax = nums[0];
        int tentativeStart = 0;
        int start = 0;
        int end = 0;

        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > currentMax + nums[i]) {
                currentMax = nums[i];
                tentativeStart = i;         // the run restarts here
            } else {
                currentMax = currentMax + nums[i];
            }
            if (currentMax > bestMax) {
                bestMax = currentMax;
                start = tentativeStart;     // freeze the winning bounds
                end = i;
            }
        }
        return new int[] { bestMax, start, end };
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        int[] result = bestSlice(nums);
        System.out.println(result[0]); // Output: 6
        System.out.println(result[1]); // Output: 3
        System.out.println(result[2]); // Output: 6
    }
}

Notice that the tentative start and the confirmed start are different things. The run may restart several times without ever beating the record, so a tentative start that never pays off simply gets overwritten later.

Run it on our example and you get a sum of 6 spanning positions 3 through 6, which is the slice [4, -1, 2, 1]. That matches the dry run exactly.

9.2 What If Every Number Is Negative?

This is the follow-up that catches the most people, and section 6.2 already traced it. The correct answer is the largest single value, because a slice must hold at least one element.

Seeding both values with the first element handles it automatically, with no special case anywhere. Seed the record at 0 instead and the code confidently returns 0, a total no slice can actually produce.

Watch for a related trap in the reset version too. The reset pushes the running total to zero, so if you record the best after resetting rather than before, an all-negative array again reports 0. Order of operations is doing quiet work in both versions.

One good habit closes this off. Whenever an algorithm seeds a best-so-far, ask what happens when no candidate ever beats the seed. If the seed itself is not a legal answer, you have a bug waiting.

10. Common Mistakes and Pitfalls

A handful of small traps catch beginners on this problem. Read them once and you will dodge most of them.

  • Seeding the record at 0. All-negative arrays then return 0, which no slice can produce. Seed with the first element instead.
  • Merging the current run and the record into one variable. The record must survive a run that later collapses.
  • Resetting before recording in the reset version. That throws away a total you never got credit for.
  • Adding up every positive number and skipping the rest. That solves the subsequence problem, and it answers 8 instead of 6 on our example.
  • Starting the compact loop at position 0. The first element is already seeded into both values, so it would count twice.
  • Assuming the winning slice holds no negatives. Our answer contains a -1, and it earns its place.
  • Quoting divide and conquer as O(n). Each level scans the whole range, so log n levels make it O(n log n).

Overflow deserves one honest sentence. On the original constraints the largest possible total is about one billion, which fits in a 32-bit integer with room to spare. Loosen those limits and a wider integer type becomes the right call.

11. Practical Walkthrough: A Best Streak Report

Let us tie everything into one small program. Instead of printing a bare number, it reads a list of daily gains and losses and reports the best stretch in a sentence a human can read.

11.1 The Program

import java.util.Arrays;

public class BestStreakReport {

    public static void report(int[] daily) {
        if (daily == null || daily.length == 0) {
            System.out.println("No days recorded.");
            return;
        }

        int currentMax = daily[0];
        int bestMax = daily[0];
        int tentativeStart = 0;
        int start = 0;
        int end = 0;

        for (int i = 1; i < daily.length; i++) {
            if (daily[i] > currentMax + daily[i]) {
                currentMax = daily[i];
                tentativeStart = i;
            } else {
                currentMax += daily[i];
            }
            if (currentMax > bestMax) {
                bestMax = currentMax;
                start = tentativeStart;
                end = i;
            }
        }

        if (bestMax < 0) {
            System.out.println("Every stretch lost money. Least bad day: day "
                    + start + " with " + bestMax);
        } else {
            int[] best = Arrays.copyOfRange(daily, start, end + 1);
            System.out.println("Best stretch: days " + start + " to " + end
                    + " " + Arrays.toString(best) + ", total " + bestMax);
        }
    }

    public static void main(String[] args) {
        report(new int[] { -2, 1, -3, 4, -1, 2, 1 });
        report(new int[] { -3, -1, -2 });
        report(new int[] { 5 });
    }
}
Best stretch: days 3 to 6 [4, -1, 2, 1], total 6
Every stretch lost money. Least bad day: day 1 with -1
Best stretch: days 0 to 0 [5], total 5

11.2 What the Output Tells You

Three inputs, three different shapes of answer. That is the point of the exercise.

  • A mixed array names the winning stretch and prints the values inside it.
  • All-negative input produces an honest message rather than a misleading total of 0.
  • The single-value array skips the loop entirely and reports that one day.

Notice how little changed from the compact version. The scan is identical. We only added a guard at the top, three position variables inside, and friendlier messages at the bottom.

Real code usually looks like this. The clever part stays tiny, and the surrounding lines exist to make the result readable and safe. Getting comfortable with that split will help you far beyond this one question.

12. Interview Questions

Q: What is the Maximum Subarray problem in DSA?

A: Somebody hands you an array of integers, and some may be negative. Your task is to find the contiguous slice with the largest sum and return that sum. The slice must be one unbroken run, so skipping elements is not allowed.

Q: How does Kadane’s Algorithm solve Maximum Subarray?

A: Kadane’s walks the array once. At each number it picks the larger of the number alone or the number added to the running total, then updates a best-so-far value. That gives O(n) time and O(1) space.

Q: What is the time complexity of Kadane’s Algorithm?

A: One single pass through the array gives O(n) time. Memory stays at O(1), because only two running values are needed no matter how long the array grows.

Q: Why seed the answer with the first element instead of zero?

A: Seeding with zero breaks all-negative arrays, since no run can beat an empty total, so the code wrongly returns zero. Seeding with the first element keeps the result correct for every input.

Q: Why do we need two variables instead of one?

A: The current run rises, falls, and sometimes restarts at a small value. A separate record keeps the best total you ever reached, so a later collapse cannot erase it.

Q: What is the difference between a subarray and a subsequence?

A: Subarrays are contiguous, so their elements are neighbours. Subsequences may skip elements freely. Solving the subsequence version by accident gives 8 on our example instead of the correct 6.

Q: What happens when every number is negative?

A: The answer is the largest single value, because a slice must hold at least one element. On [-3, -1, -2] the result is -1, and correct seeding handles that with no special case.

Q: How does the divide and conquer solution work?

A: Split the array at the middle, then take the largest of three candidates: the best slice on the left, the best on the right, and the best one crossing the centre. Total cost is O(n log n) time.

Q: Can Maximum Subarray also return the subarray itself?

A: Yes. Track a tentative start and move it whenever the run restarts, then freeze the start and end positions whenever the record improves. That returns the actual slice alongside the sum.

Q: Which edge cases should I test before I say I am done?

A: Try a single element, an all-negative array, an all-positive array, one where the winning slice contains a negative, and one where the answer sits at the very end. Those five inputs catch nearly every bug here.

13. Conclusion

Let us wrap up what we covered. The Maximum Subarray problem in DSA looks small, and it is. Yet Kadane’s Algorithm hiding inside it is a real skill you will reuse constantly.

We started with brute force, trying every left edge against every right edge. Correct, honest, and slow at O(n squared). Its dry run showed six slices for three numbers, with a single element taking the crown.

Kadane’s came next, and we built it in two steps. First the literal version, which resets the running total the moment it turns negative. Then the compact form, which folds that reset into one comparison between restarting and extending. Both are the same idea, and both cost O(n) time with O(1) space.

Divide and conquer closed the set. Split at the middle, solve both halves, measure the best crossing slice, and take the largest of the three. Slower at O(n log n), and worth knowing because interviewers ask for it.

Along the way we handled the usual follow-ups. Reporting the slice itself takes three extra variables. All-negative arrays need no special case at all, provided you seed from the first element rather than zero.

So take the pattern, not just the answer. Walk once, carry a running total, drop it when it stops helping, and photograph your best moment as you pass it. Once that habit feels natural, a whole family of array and dynamic programming problems opens up.

14. Further Reading

Leave a Comment