[The Beauty of Programming] Max Sum of Subarray

Algorithm 1 – O(n^2): enumerate all possible subarray
This is pretty straightforward, but second program improves the performance by not repeating calculating sum.

def max_subarray_enum(l):
    """
    >>> l = [43, 23, -3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_enum(l))
    95
    >>> l = [-3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_enum(l))
    32
    """
    length = len(l)
    def sum(i, len):
        # assertion
        val = 0
        for n in range(0, len):
            val += l[i + n]
        return val
    # loop invariant:
    # l[i:i+len] is subarray which we need to calculate the sum
    # termination: enumerated all possible i j combinations
    max_val = l[0]
    for i in range(0, length):
        for subarray_len in range(1, length - i + 1):
            val = sum(i, subarray_len)
            if val > max_val: max_val = val
    return max_val
def max_subarray_enum2(l):
    """
    >>> l = [43, 23, -3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_enum2(l))
    95
    >>> l = [-3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_enum2(l))
    32
    """
    length = len(l)
    # loop invariant:
    # l[i:i+len] is subarray which we need to calculate the sum
    # termination: enumerated all possible i j combinations
    max_val = l[0]
    for i in range(0, length):
        val = 0
        for subarray_len in range(1, length - i + 1):
            val += l[i + subarray_len - 1]
            if val > max_val: max_val = val
    return max_val

Algorithm 2 – O(nlogn): divide and conquer

def max_subarray_divide(l, left, right):
    """
    >>> l = [43, 23, -3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_divide(l, 0, len(l) - 1))
    95
    >>> l = [-3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_divide(l, 0, len(l) - 1))
    32
    """
    # divide and conquer:
    # max = max_val(
    #   recur(firsthalf),
    #   recur(secondhalf),
    #   max_sum_left + max_sum_right
    # )
    def max_val(vals):
        # TODO: assert
        max = vals[0]
        for val in vals:
            if max < val: max = val
        return max
    def max_sum(start, end, step):
        if step > 0 and start > end:
            return 0
        elif step < 0 and end > start:
            return 0
        index_range = range(start, end + step, step)
        max = l[index_range[0]]
        sum = max
        for i in index_range[1:]:
            sum += l[i]
            if max < sum: max = sum
        return max

    if left > right:
        return 0  # actually, this one should be minus infinity
    if left == right:
        return l[left]

    mid = left + ((right - left) >> 1)
    # NOTE for the following statement, if we put max_sum(mid, right, 1)
    # then there will be dead recursive loop
    max_sum_right = max_sum(mid + 1, right, 1)
    max_sum_left = max_sum(mid, left, -1)
    max_sum_first = max_subarray_divide(l, left, mid)
    max_sum_right = max_subarray_divide(l, mid + 1, right)

    return max_val([max_sum_first, max_sum_right, max_sum_left + max_sum_right])

Algorithm 3 – O(n) with O(n) space: dynamic programming

def max_subarray(l):
    """
    >>> l = [43, 23, -3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray(l))
    95
    >>> l = [-3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray(l))
    32
    """
    # following are the dynamic programming vars:
    # - tail_max[n] means max subarray that ends at position n
    # - max[n] means max subarray in array l[0:n+1]
    #
    # following are the dynamic programming equations:
    # - tail_max[n] = max(tail_max[n-1]+l[n], l[n])
    # - max[n] = max(max[n-1], tail_max[n])
    #
    def max_val(a, b):
        return a if a > b else b
    # init data structures
    length = len(l)
    max = [None] * length
    tail_max = [None] * length
    # initial values
    if len(l) == 0:
        return 0
    max[0] = l[0]
    tail_max[0] = l[0]
    # dynamic programming: loop
    for n in range(1, length):
        tail_max[n] = max_val(tail_max[n-1]+l[n], l[n])
        max[n] = max_val(max[n-1], tail_max[n])
    return max[n]

Algorithm 4 – O(n) with O(1) space: dynamic programming with improvement

def max_subarray_o1space(l):
    """
    >>> l = [43, 23, -3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_o1space(l))
    95
    >>> l = [-3, 32, -43, -22, 12, 3, -4, 5]
    >>> print(max_subarray_o1space(l))
    32
    """
    def max_val(a, b):
        return a if a > b else b
    # initial values
    if len(l) == 0:
        return 0
    max_prev = l[0]
    tail_max_prev = l[0]
    # init data structures
    max = max_prev
    tail_max = tail_max_prev
    # dynamic programming: loop
    for n in range(1, len(l)):
        tail_max = max_val(tail_max_prev+l[n], l[n])
        max = max_val(max_prev, tail_max)
        tail_max_prev = tail_max
        max_prev = max
    return max

2D array?…
OK, I’ll ignore the code here….but it can be converted to 1D array like:

for upper_bound in range(0, first_dim_length(matrix)):
  for lower_bound in range(upper_bound, first_dim_length(matrix):
    merge the 2D array in within upper and lower bound to 1D array
    use dynamic programming approach to solve it

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>