Maximum subarray divide and conquer in Python
Split at the midpoint, take the best of the left, right and crossing subarrays, giving Theta(n log n) time.
- File
maximum_subarray_divide_and_conquer.py, 70 lines- Level
- I0 not checked yet
- Toolchain
- Python 3.14, pytest 9
- Test framework
- pytest
- Run the tests
python3 -m pytest
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/maximum-subarray-divide-and-conquer/. The authors are listed in prov/NXBA0T.jsonl.
from typing import Union
Number = Union[int, float]
def _maxsub(a: list[Number], lo: int, hi: int) -> tuple[Number, int, int]:
if lo == hi: # @line L1
return a[lo], lo, lo
mid = (lo + hi) // 2 # @line L2
s_left, i_left, j_left = _maxsub(a, lo, mid) # @line L3
s_right, i_right, j_right = _maxsub(a, mid + 1, hi)
t: Number = 0 # @line L4
left_sum: Number = a[mid]
ci = mid
for i in range(mid, lo - 1, -1):
t += a[i]
if t > left_sum or i == mid:
left_sum = t
ci = i
t = 0 # @line L5
right_sum: Number = a[mid + 1]
cj = mid + 1
for j in range(mid + 1, hi + 1):
t += a[j]
if t > right_sum or j == mid + 1:
right_sum = t
cj = j
s_cross = left_sum + right_sum # @line L6
if s_left >= s_right and s_left >= s_cross: # @line L7
return s_left, i_left, j_left
if s_right >= s_cross: # @line L8
return s_right, i_right, j_right
return s_cross, ci, cj # @line L9
def max_subarray_dc(a: list[Number]) -> tuple[Number, int, int]:
"""Return (best, i, j) with sum(a[i:j + 1]) == best, the largest sum of a non-empty subarray.
Indices are 0-based and inclusive. Ties prefer the left half, then the right
half, then the crossing subarray.
Precondition: a is not empty (raises ValueError otherwise).
Cost: Theta(n log n) time, Theta(log n) extra space for the recursion.
"""
if not a:
raise ValueError("the array must not be empty")
return _maxsub(a, 0, len(a) - 1)
def max_subarray_linear_dc(a: list[Number]) -> Number:
"""Return the largest non-empty subarray sum with the four-value combine.
Each part returns (total, best prefix, best suffix, best). Cost: Theta(n)
time, Theta(log n) extra space. Precondition: a is not empty.
"""
if not a:
raise ValueError("the array must not be empty")
def solve(lo: int, hi: int) -> tuple[Number, Number, Number, Number]:
if lo == hi:
x = a[lo]
return x, x, x, x
mid = (lo + hi) // 2
lt, lp, ls, lb = solve(lo, mid)
rt, rp, rs, rb = solve(mid + 1, hi)
return lt + rt, max(lp, lt + rp), max(rs, rt + ls), max(lb, rb, ls + rp)
return solve(0, len(a) - 1)[3]
test_maximum_subarray_divide_and_conquer.py 45 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/maximum-subarray-divide-and-conquer/. The authors are listed in prov/NXBA0T.jsonl.
import random
import pytest
from maximum_subarray_divide_and_conquer import max_subarray_dc, max_subarray_linear_dc
def brute(a: list[int]) -> int:
return max(sum(a[i : j + 1]) for i in range(len(a)) for j in range(i, len(a)))
def test_example() -> None:
assert max_subarray_dc([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == (6, 3, 6)
assert max_subarray_dc([31, -41, 59, 26, -53, 58, 97, -93, -23, 84]) == (187, 2, 6)
clrs = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
assert max_subarray_dc(clrs) == (43, 7, 10)
assert max_subarray_linear_dc(clrs) == 43
def test_edges() -> None:
assert max_subarray_dc([5]) == (5, 0, 0)
assert max_subarray_dc([-5]) == (-5, 0, 0)
assert max_subarray_dc([-3, -1, -2]) == (-1, 1, 1)
assert max_subarray_dc([1, 2, 3]) == (6, 0, 2)
assert max_subarray_dc([1.5, -0.5, 2.0]) == (3.0, 0, 2)
assert max_subarray_linear_dc([-3, -1, -2]) == -1
with pytest.raises(ValueError):
max_subarray_dc([])
with pytest.raises(ValueError):
max_subarray_linear_dc([])
def test_against_brute_force() -> None:
rng = random.Random(20261105)
for _ in range(2000):
n = rng.randrange(1, 30)
a = [rng.randrange(-10, 11) for _ in range(n)]
best, i, j = max_subarray_dc(a)
assert best == brute(a)
assert 0 <= i <= j < n
assert sum(a[i : j + 1]) == best
assert max_subarray_linear_dc(a) == best
What the next level needs
- the tests did not run in this build
Read the full page about Maximum subarray divide and conquer