title: Maximum Subarray slug: maximum-subarray difficulty: medium leetcode_id: 53 leetcode_url: https://leetcode.com/problems/maximum-subarray/ categories: - arrays - dynamic-programming patterns: - slug: dynamic-programming is_optimal: true function_signature: "def max_subarray(nums: list[int]) -> int:" test_cases: visible: - input: { nums: [-2, 1, -3, 4, -1, 2, 1, -5, 4] } expected: 6 - input: { nums: [1] } expected: 1 - input: { nums: [5, 4, -1, 7, 8] } expected: 23 hidden: - input: { nums: [-1] } expected: -1 - input: { nums: [-2, -1] } expected: -1 - input: { nums: [1, 2, 3, 4] } expected: 10 - input: { nums: [-1, -2, -3, -4] } expected: -1 - input: { nums: [3, -2, 5, -1] } expected: 6 - input: { nums: [-2, 1] } expected: 1 - input: { nums: [8, -19, 5, -4, 20] } expected: 21 description: | Given an integer array `nums`, find the subarray with the largest sum, and return *its sum*. A **subarray** is a contiguous non-empty sequence of elements within an array. constraints: | - `1 <= nums.length <= 10^5` - `-10^4 <= nums[i] <= 10^4` examples: - input: "nums = [-2,1,-3,4,-1,2,1,-5,4]" output: "6" explanation: "The subarray [4,-1,2,1] has the largest sum 6." - input: "nums = [1]" output: "1" explanation: "The subarray [1] has the largest sum 1." - input: "nums = [5,4,-1,7,8]" output: "23" explanation: "The entire array [5,4,-1,7,8] has the largest sum 23." explanation: intuition: | Imagine you're walking along a number line, collecting values. At each step, you face a simple decision: should you keep accumulating, or cut your losses and start fresh? Think of it like this: you're tracking a "running sum" as you traverse the array. If your running sum becomes negative, it's **dragging you down** — any future subarray would be better off starting fresh without that negative baggage. The key insight of **Kadane's Algorithm** is that at each position `i`, you have exactly two choices: 1. **Extend** the previous subarray by adding `nums[i]` (if the previous sum helps) 2. **Start fresh** at `nums[i]` (if the previous sum was negative) Mathematically: `current_sum = max(nums[i], current_sum + nums[i])` We also track the maximum sum seen so far, because the optimal subarray might end before the last element. approach: | We solve this using **Kadane's Algorithm**: **Step 1: Initialise tracking variables** - `current_sum = nums[0]`: The sum of the current subarray ending at position i - `max_sum = nums[0]`: The maximum sum we've found so far - Starting with `nums[0]` (not 0) handles all-negative arrays correctly   **Step 2: Iterate from index 1 to the end** - For each element `nums[i]`: - **Decide**: extend or restart? `current_sum = max(nums[i], current_sum + nums[i])` - If `current_sum + nums[i] >= nums[i]`, extend (previous sum is non-negative) - If `current_sum + nums[i] < nums[i]`, restart (previous sum was negative) - **Update maximum**: `max_sum = max(max_sum, current_sum)`   **Step 3: Return the result** - Return `max_sum` — the largest subarray sum encountered - Note: we don't return `current_sum` because the optimal subarray might have ended earlier   This elegant algorithm makes the locally optimal choice at each step (extend or restart), which leads to the globally optimal solution. common_pitfalls: - title: Initialising max_sum to 0 description: | If all elements are negative (e.g., `[-3, -2, -1]`), the maximum subarray sum is `-1`, not `0`. Initialising `max_sum = 0` would incorrectly return `0` for such arrays, as if an empty subarray were valid (it's not — the problem requires a non-empty subarray). Always initialise with `nums[0]` to guarantee a valid answer. wrong_approach: "max_sum = 0" correct_approach: "max_sum = nums[0]" - title: Only Returning current_sum at the End description: | The maximum subarray doesn't necessarily include the last element! Consider `[4, -1, 2, 1, -5]`: the maximum sum is `6` (subarray `[4, -1, 2, 1]`), but `current_sum` at the end is `1`. You must track `max_sum` throughout the traversal and update it whenever `current_sum` exceeds it. wrong_approach: "return current_sum" correct_approach: "Track max_sum and return it" - title: Confusing Subarray with Subsequence description: | A **subarray** must be contiguous — you cannot skip elements. This is why Kadane's algorithm works: at each position, the subarray either extends from the previous position or starts fresh. A **subsequence** allows skipping elements, which would require a different approach entirely. wrong_approach: "Thinking you can skip elements" correct_approach: "Subarray = contiguous sequence" key_takeaways: - "**Kadane's Algorithm**: The classic O(n) solution for maximum subarray — memorise it!" - "**Local vs global optimum**: Sometimes making locally optimal choices (extend or restart) yields the global optimum" - "**Initialisation matters**: Using `nums[0]` instead of `0` handles edge cases correctly" - "**Pattern recognition**: This 'extend or restart' logic applies to many contiguous subarray problems" time_complexity: "O(n). Single pass through the array, with O(1) work at each position." space_complexity: "O(1). Only two variables (`current_sum` and `max_sum`) regardless of input size." solutions: - approach_name: Kadane's Algorithm is_optimal: true code: | def max_subarray(nums: list[int]) -> int: # Initialise with first element (handles all-negative arrays) current_sum = nums[0] max_sum = nums[0] # Process remaining elements for i in range(1, len(nums)): # Decision: extend previous subarray or start fresh? # Extend if previous sum helps, otherwise restart current_sum = max(nums[i], current_sum + nums[i]) # Update maximum if current subarray is better max_sum = max(max_sum, current_sum) return max_sum explanation: | **Time Complexity:** O(n) — Single pass through the array. **Space Complexity:** O(1) — Only two variables needed. At each position, we decide whether to extend the previous subarray or start a new one. We track the maximum sum seen throughout the traversal. This greedy choice at each step produces the globally optimal result. - approach_name: Brute Force is_optimal: false code: | def max_subarray(nums: list[int]) -> int: n = len(nums) max_sum = nums[0] # Try every possible starting point for i in range(n): current_sum = 0 # Try every possible ending point from i for j in range(i, n): current_sum += nums[j] max_sum = max(max_sum, current_sum) return max_sum explanation: | **Time Complexity:** O(n²) — Nested loops checking all subarrays. **Space Complexity:** O(1) — Only tracking sums. This approach tries every possible subarray by fixing a start index and extending to each possible end index. While correct, it's too slow for large inputs (up to 10 billion operations for n = 10^5).