Find Maximum Subarray Divide and Conquer in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find the maximum subarray grows as the input size increases.
Specifically, we ask: how does the divide and conquer method scale with bigger arrays?
Analyze the time complexity of the following code snippet.
fn max_crossing_subarray(arr: &[i32], low: usize, mid: usize, high: usize) -> i32 {
let mut left_sum = i32::MIN;
let mut sum = 0;
for i in (low..=mid).rev() {
sum += arr[i];
if sum > left_sum {
left_sum = sum;
}
}
let mut right_sum = i32::MIN;
sum = 0;
for j in mid+1..=high {
sum += arr[j];
if sum > right_sum {
right_sum = sum;
}
}
left_sum + right_sum
}
fn max_subarray(arr: &[i32], low: usize, high: usize) -> i32 {
if low == high {
return arr[low];
}
let mid = (low + high) / 2;
let left_max = max_subarray(arr, low, mid);
let right_max = max_subarray(arr, mid + 1, high);
let cross_max = max_crossing_subarray(arr, low, mid, high);
*[left_max, right_max, cross_max].iter().max().unwrap()
}
This code finds the maximum sum of a contiguous subarray using divide and conquer.
- Primary operation: Recursive calls splitting the array into halves.
- How many times: Each recursive call splits the array until size 1, so about log n levels.
- Secondary operation: The crossing subarray calculation loops over parts of the array once per call.
- How many times: At each level, the crossing sum loops cover the entire array once in total.
At each level of recursion, the crossing sum loops run over the whole array once combined.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 * log2(10) ≈ 33 |
| 100 | About 100 * log2(100) ≈ 664 |
| 1000 | About 1000 * log2(1000) ≈ 9966 |
Pattern observation: The operations grow a bit faster than linear but much slower than quadratic, roughly n times log n.
Time Complexity: O(n log n)
This means the time needed grows a little more than directly with input size, but still efficient for large inputs.
[X] Wrong: "Since the code uses recursion, it must be very slow like O(n²)."
[OK] Correct: The recursion splits the problem in half each time, and the crossing sums only add a linear cost per level, so total work is n log n, not n squared.
Understanding this time complexity helps you explain how divide and conquer can speed up problems by breaking them down efficiently.
"What if we replaced the crossing sum loops with a linear scan that keeps track of sums without restarting? How would the time complexity change?"
Practice
Divide and Conquer approach to find the maximum subarray in an array?Solution
Step 1: Understand the divide step
The array is split into two halves to simplify the problem into smaller parts.Step 2: Combine results from subproblems
Find max subarray in left half, right half, and subarray crossing the middle, then pick the maximum among these.Final Answer:
Split array, solve halves, combine crossing max -> Option AQuick Check:
Divide and conquer = split and combine max [OK]
- Thinking sorting helps find max subarray
- Ignoring subarray crossing the middle
- Assuming max subarray is always a single element
Solution
Step 1: Identify correct parameter types
The function should take a slice reference and indices for left and right bounds.Step 2: Identify correct return type
The function returns the maximum sum as an integer (i32).Final Answer:
fn max_subarray(arr: &[i32], left: usize, right: usize) -> i32 -> Option BQuick Check:
Slice + indices + i32 return = fn max_subarray(arr: &[i32], left: usize, right: usize) -> i32 [OK]
- Using Vec instead of slice reference
- Returning wrong type like bool or String
- Missing left and right index parameters
fn max_crossing_sum(arr: &[i32], left: usize, mid: usize, right: usize) -> i32 {
let mut sum = 0;
let mut left_sum = i32::MIN;
for i in (left..=mid).rev() {
sum += arr[i];
if sum > left_sum {
left_sum = sum;
}
}
sum = 0;
let mut right_sum = i32::MIN;
for i in mid+1..=right {
sum += arr[i];
if sum > right_sum {
right_sum = sum;
}
}
left_sum + right_sum
}
fn main() {
let arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
let result = max_crossing_sum(&arr, 0, 4, 8);
println!("{}", result);
}Solution
Step 1: Calculate left sum from mid=4 to left=0
Sum backward: i=4: sum=-1, left_sum=-1; i=3: sum=3, left_sum=3; i=2: sum=0; i=1: sum=1; i=0: sum=-1; max left_sum = 3Step 2: Calculate right sum from mid+1=5 to right=8
Sum forward: i=5: sum=2, right_sum=2; i=6: sum=3, right_sum=3; i=7: sum=-2; i=8: sum=2; max right_sum = 3Step 3: Add left_sum and right_sum
3 + 3 = 6Final Answer:
6 -> Option AQuick Check:
Max crossing sum = 6 [OK]
- Not reversing left loop correctly
- Adding sums incorrectly
- Using wrong indices for loops
fn max_subarray(arr: &[i32], left: usize, right: usize) -> i32 {
if left == right {
return arr[left];
}
let mid = (left + right) / 2;
let left_sum = max_subarray(arr, left, mid);
let right_sum = max_subarray(arr, mid + 1, right);
let cross_sum = max_crossing_sum(arr, left, mid, right);
left_sum.max(right_sum).max(cross_sum)
}
What is the likely cause of a runtime panic when calling this function with an empty array?Solution
Step 1: Check base case for empty array
If array is empty, left and right indices are invalid, so arr[left] access causes panic.Step 2: Understand panic cause
Accessing arr[left] when left == right but array length is zero triggers out-of-bounds error.Final Answer:
Accessing arr[left] on empty array causes panic -> Option DQuick Check:
Empty array access = panic [OK]
- Ignoring empty array edge case
- Assuming mid calculation causes overflow
- Thinking max_crossing_sum absence causes panic
[3, -2, 5, -1, 6, -3, 2, 7, -5, 2], what is the maximum subarray sum found using the divide and conquer approach?Solution
Step 1: Identify max subarray candidates
Check subarrays: [3, -2, 5, -1, 6, -3, 2, 7] sums to 17, [5, -1, 6, -3, 2, 7] sums to 16, [3, -2, 5, -1, 6] sums to 11.Step 2: Find max sum subarray
The subarray [3, -2, 5, -1, 6, -3, 2, 7] has the maximum sum 17.Final Answer:
17 -> Option CQuick Check:
Max sum subarray = 17 [OK]
- Choosing sum of entire array
- Ignoring negative values inside subarray
- Not considering crossing subarrays
Ready to prove it?
Pass the test and this pattern moves unknown
