Recommended resources
The freely available resources this lesson will direct you to.
Welcome back! In our last lesson, we solidified your understanding of Kadane's algorithm by translating its core logic into a robust implementation. We focused on finding the maximum subarray sum and correctly handling the important edge case of an all-negative array.
This lesson builds directly on that foundation to address a common and practical follow-up: finding the subarray itself. Your goal for this lesson is to learn how to adapt Kadane's algorithm to track the start and end indices of the maximum subarray. Knowing not just the "what" (the sum) but also the "where" (the indices) is a frequent requirement in real-world applications and coding challenges, from analyzing stock market trends to processing data streams.
The Logic: Augmenting Kadane's Algorithm
The beauty of Kadane's algorithm is its simplicity, and thankfully, we can preserve that while adding the logic to track indices. We'll need to introduce a few new variables to our existing implementation.
Recall the core of the algorithm from our previous lesson: we iterate through the array, maintaining a current_sum. If this current_sum becomes negative, we discard it and reset it to 0. This very action—discarding a useless prefix—gives us the crucial signal we need. It tells us that any potential new maximum subarray must start from the next element.
Here is the strategy we'll employ:
max_sumandcurrent_sum: These behave exactly as before.max_sumtracks the best sum found so far, andcurrent_sumtracks the sum of the subarray ending at the current position.potential_start: We'll use a variable to keep track of the starting index of the current subarray candidate. Whencurrent_sumdrops below zero and we reset it, it means the subarray we were tracking is no longer a candidate. We must therefore updatepotential_startto the next index (i + 1).final_startandfinal_end: These variables will store the official start and end indices of the overall maximum subarray found so far. We only update these when we find a newmax_sum—that is, whencurrent_sum > max_sum. When this happens,final_endis simply the current indexi, andfinal_startis thepotential_startwe've been tracking.
The take U forward channel provides an excellent visual walkthrough of this exact logic.
Kadane's Algorithm | Maximum Subarray Sum | Finding and Printing
This video explains how to modify Kadane's algorithm to not only find the sum but also identify the subarray itself.
Please watch the segment from this explanation on printing the subarray. The key insight to focus on is how the start of a potential subarray is marked whenever the running sum is reset to zero. The presenter uses a start variable (which corresponds to our potential_start) and updates the final answer_start and answer_end only when a new maximum is found.
From Logic to Code
As you saw in the video, the modifications are quite small and fit neatly into the existing loop. Let's translate this into a Python implementation.
def max_subarray_with_indices(nums):
if not nums:
return 0, -1, -1 # Or some other indication of an empty array
max_so_far = float('-inf')
current_sum = 0
start_index = 0
end_index = -1
potential_start = 0
for i, num in enumerate(nums):
current_sum += num
if current_sum > max_so_far:
max_so_far = current_sum
start_index = potential_start
end_index = i
if current_sum < 0:
current_sum = 0
potential_start = i + 1
# Handle the all-negative case separately
# If max_so_far is still negative, it means we never had a positive current_sum.
# The true max is a single element.
if max_so_far < 0:
max_so_far = float('-inf')
for i, num in enumerate(nums):
if num > max_so_far:
max_so_far = num
start_index = i
end_index = i
return max_so_far, start_index, end_index
# Example usage:
arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
max_sum, start, end = max_subarray_with_indices(arr)
print(f"Max sum: {max_sum}, Subarray: {arr[start:end+1]}")
# Output: Max sum: 6, Subarray: [4, -1, 2, 1]
arr_neg = [-5, -2, -3]
max_sum, start, end = max_subarray_with_indices(arr_neg)
print(f"Max sum: {max_sum}, Subarray: {arr_neg[start:end+1]}")
# Output: Max sum: -2, Subarray: [-2]
Notice the small complication for the all-negative case. Our simple logic relies on current_sum becoming positive at some point. If all numbers are negative, max_so_far will be updated correctly (as we saw in the previous lesson), but the indices might not be. The most straightforward way to handle this is to run the basic potential_start logic and then, if the final max_so_far is negative, we know the answer must be the single largest element. The code above shows one way to correct this.
A slightly cleaner approach integrates this logic directly, as shown in the following resource.
Kadane's Algorithm for Maximum Subarray in Java
This article from Alexander Obregon's Substack provides a concise Java implementation and discusses the rationale for tracking indices.
Please read the section on tracking indices. The Java code provided uses variables start, end, and finalStart which map directly to our potential_start, end_index, and start_index. The logic is essentially identical to what we've discussed and provides a good reinforcement of the pattern.
Walkthrough: A Step-by-Step Trace
Let's trace our algorithm with the array arr = [4, -1, 2, 1, -5, 4] to see the variables in action. We'll use a slightly more elegant implementation that correctly handles all-negative cases without a second pass.
The key is to initialize max_so_far with the first element and start the loop from there.
i |
num |
current_sum |
potential_start |
max_so_far |
start_index |
end_index |
Action |
|---|---|---|---|---|---|---|---|
| - | - | 0 | 0 | -inf | -1 | -1 | Initial State |
| 0 | 4 | 4 | 0 | 4 | 0 | 0 | current_sum > max_so_far. Update max, start, end. |
| 1 | -1 | 3 | 0 | 4 | 0 | 0 | current_sum is positive. No change to max or indices. |
| 2 | 2 | 5 | 0 | 5 | 0 | 2 | current_sum > max_so_far. Update max, start, end. |
| 3 | 1 | 6 | 0 | 6 | 0 | 3 | current_sum > max_so_far. Update max, start, end. |
| 4 | -5 | 1 | 0 | 6 | 0 | 3 | current_sum is positive. No change to max or indices. |
| 5 | 4 | 5 | 0 | 6 | 0 | 3 | current_sum is positive. No change to max or indices. |
Let's use the other example arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4].
i |
num |
current_sum |
potential_start |
max_so_far |
start_index |
end_index |
Action |
|---|---|---|---|---|---|---|---|
| - | - | 0 | 0 | -inf | -1 | -1 | Initial State |
| 0 | -2 | -2 | 0 | -2 | 0 | 0 | Update max, start, end. current_sum < 0, reset current_sum=0, potential_start=1. |
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | Update max, start, end. current_sum is positive. |
| 2 | -3 | -2 | 1 | 1 | 1 | 1 | current_sum < 0, reset current_sum=0, potential_start=3. |
| 3 | 4 | 4 | 3 | 4 | 3 | 3 | Update max, start, end. current_sum is positive. |
| 4 | -1 | 3 | 3 | 4 | 3 | 3 | current_sum is positive. No change. |
| 5 | 2 | 5 | 3 | 5 | 3 | 5 | Update max, start, end. current_sum is positive. |
| 6 | 1 | 6 | 3 | 6 | 3 | 6 | Update max, start, end. current_sum is positive. |
| 7 | -5 | 1 | 3 | 6 | 3 | 6 | current_sum is positive. No change. |
| 8 | 4 | 5 | 3 | 6 | 3 | 6 | current_sum is positive. No change. |
The final answer is max_sum = 6, starting at index 3 and ending at index 6.
Conclusion
You have now extended Kadane's algorithm to solve the complete version of the problem: finding not only the maximum sum but also the subarray that produces it. This is a powerful and efficient pattern to have in your toolkit.
Key Takeaways:
- The Signal: The moment Kadane's algorithm resets the
current_sumto zero is the signal that a new potential subarray begins at the next index. - Variable Augmentation: We can track indices by adding three variables: a
potential_startthat resets withcurrent_sum, and afinal_startandfinal_endthat are updated only when a newmax_so_faris discovered. - Robustness: Handling the all-negative case requires a slight modification or a simple check to ensure the single largest element is returned correctly.
In our next lesson, we will explore how this powerful pattern can serve as a subroutine for solving more complex problems. Specifically, we will apply Kadane's algorithm to variants such as the maximum circular subarray sum, where the array can "wrap around."