Find Maximum Subarray Divide and Conquer in DSA Go - Time & Space Complexity
We want to understand how the time needed to find the maximum subarray grows as the input array gets bigger.
Specifically, how does the divide and conquer method affect the work done?
Analyze the time complexity of the following code snippet.
func maxSubArray(arr []int, low, high int) int {
if low == high {
return arr[low]
}
mid := (low + high) / 2
leftMax := maxSubArray(arr, low, mid)
rightMax := maxSubArray(arr, mid+1, high)
crossMax := maxCrossingSubArray(arr, low, mid, high)
return max(leftMax, rightMax, crossMax)
}
func maxCrossingSubArray(arr []int, low, mid, high int) int {
leftSum := -1 << 31
sum := 0
for i := mid; i >= low; i-- {
sum += arr[i]
if sum > leftSum {
leftSum = sum
}
}
rightSum := -1 << 31
sum = 0
for j := mid + 1; j <= high; j++ {
sum += arr[j]
if sum > rightSum {
rightSum = sum
}
}
return leftSum + rightSum
}
This code finds the maximum sum of a subarray by splitting the array, solving for left, right, and crossing parts, then combining results.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls splitting the array into halves.
- How many times: The array is split until single elements remain, about log n levels.
- Additional operation: At each level, the crossing subarray is found by scanning left and right parts once (linear scan).
At each level, the work to find the crossing max is proportional to the size of the current subarray.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 * log 10 ≈ 33 |
| 100 | About 100 * log 100 ≈ 664 |
| 1000 | About 1000 * log 1000 ≈ 9966 |
Pattern observation: As input size grows, total work grows a bit more than linearly, roughly n times log n.
Time Complexity: O(n log n)
This means the time needed grows a little faster than the size of the input, because we do linear work at each level of a logarithmic number of levels.
[X] Wrong: "Since the array is split in half each time, the time complexity is O(log n)."
[OK] Correct: Although the array splits reduce problem size, at each level we still scan parts of the array linearly, so total work adds up to more than just log n.
Understanding this time complexity helps you explain how divide and conquer can speed up problems compared to simple loops, showing your grasp of algorithm design.
"What if we replaced the crossing subarray scan with a constant time operation? How would the time complexity change?"
Practice
Divide and Conquer approach to find the maximum subarray sum?Solution
Step 1: Understand divide and conquer strategy
The array is split into two halves recursively to simplify the problem.Step 2: Combine results from subproblems
Find max subarray in left half, right half, and crossing the middle, then take the maximum of these three.Final Answer:
Split array, find max in left, right, crossing middle, combine -> Option AQuick Check:
Divide and conquer = split and combine max subarrays [OK]
- Thinking sorting helps find max subarray sum
- Confusing divide and conquer with sliding window
- Calculating all subarrays sums directly
arr between indices low and high?Solution
Step 1: Check Go slice and parameter syntax
In Go, slices are declared as []int, and multiple parameters of same type can be grouped.Step 2: Identify correct function signature
func maxSubArray(arr []int, low, high int) int uses correct slice type and groups low, high as int parameters, returning int sum.Final Answer:
func maxSubArray(arr []int, low, high int) int -> Option DQuick Check:
Go slice syntax and grouped params = func maxSubArray(arr []int, low, high int) int [OK]
- Using int[] instead of []int for slices
- Returning multiple values when only sum needed
- Separating same-type parameters unnecessarily
package main
import "fmt"
func maxCrossingSum(arr []int, low, mid, high int) int {
leftSum := -1 << 31
sum := 0
for i := mid; i >= low; i-- {
sum += arr[i]
if sum > leftSum {
leftSum = sum
}
}
rightSum := -1 << 31
sum = 0
for j := mid + 1; j <= high; j++ {
sum += arr[j]
if sum > rightSum {
rightSum = sum
}
}
return leftSum + rightSum
}
func maxSubArray(arr []int, low, high int) int {
if low == high {
return arr[low]
}
mid := (low + high) / 2
leftMax := maxSubArray(arr, low, mid)
rightMax := maxSubArray(arr, mid+1, high)
crossMax := maxCrossingSum(arr, low, mid, high)
if leftMax >= rightMax && leftMax >= crossMax {
return leftMax
} else if rightMax >= leftMax && rightMax >= crossMax {
return rightMax
} else {
return crossMax
}
}
func main() {
arr := []int{-2, 1, -3, 4, -1, 2, 1, -5, 4}
fmt.Println(maxSubArray(arr, 0, len(arr)-1))
}Solution
Step 1: Understand the input array and max subarray
The array is [-2,1,-3,4,-1,2,1,-5,4]. The max subarray is [4,-1,2,1] with sum 6.Step 2: Trace divide and conquer calls
The function recursively finds max in left, right, and crossing middle, correctly returning 6.Final Answer:
6 -> Option CQuick Check:
Max subarray sum = 6 [OK]
- Choosing sum including -5 which lowers total
- Confusing max crossing sum with max overall
- Miscomputing indices for mid and crossing sums
func maxCrossingSum(arr []int, low, mid, high int) int {
leftSum := 0
sum := 0
for i := mid; i >= low; i-- {
sum += arr[i]
if sum > leftSum {
leftSum = sum
}
}
rightSum := 0
sum = 0
for j := mid + 1; j <= high; j++ {
sum += arr[j]
if sum > rightSum {
rightSum = sum
}
}
return leftSum + rightSum
}Solution
Step 1: Analyze initialization of leftSum and rightSum
They start at 0, which fails if all numbers are negative, missing correct max sums.Step 2: Fix initialization to very small number
Set leftSum and rightSum to -1<<31 (minimum int) to correctly track max sums even if negative.Final Answer:
Initialize leftSum and rightSum to very small number (e.g. -1<<31) -> Option AQuick Check:
Proper initialization avoids missing negative max sums [OK]
- Starting max sums at zero causes wrong results with negatives
- Changing loop directions incorrectly
- Returning sum instead of combined max sums
arr of integers, how would you modify the divide and conquer approach to also return the start and end indices of the maximum subarray along with its sum?Solution
Step 1: Modify function return types
Change functions to return a struct containing sum, start index, and end index to track subarray boundaries.Step 2: Update recursive calls and combine logic
When combining left, right, and crossing results, also combine their indices accordingly to keep track of max subarray range.Final Answer:
Return struct with sum, start, end; update recursive calls to track indices -> Option BQuick Check:
Return sum and indices together for full max subarray info [OK]
- Using global variables causing bugs in recursion
- Finding indices after sum calculation separately
- Ignoring indices in crossing sum calculation
Ready to prove it?
Pass the test and this pattern moves unknown
