Kadane算法,是如何求解最大子数组和的?

48 阅读5分钟

Kadane's 算法是一种高效解决最大子数组和问题的动态规划算法。它通过迭代数组并维护两个变量来动态更新局部和全局的最大子数组和,最终返回全局最大值。以下是算法的详细解释及步骤:

算法原理

在给定的整数数组中找到一个连续的子数组,使得子数组的和最大。该问题的关键在于数组中可能包含负数。

步骤

  1. 初始化

    • maxEndingHere 表示以当前位置为结束的最大子数组和,初始值为数组的第一个元素。
    • maxSoFar 表示全局的最大子数组和,初始值也为数组的第一个元素。
  2. 迭代

    • 从数组的第二个元素开始,依次遍历每个元素。
    • 对于每个元素,更新 maxEndingHere 的值:
      • maxEndingHere = max(nums[i], maxEndingHere + nums[i])
      • 这表示我们要么继续扩展当前的子数组,要么从当前位置重新开始一个新的子数组。
    • 更新全局最大子数组和:
      • maxSoFar = max(maxSoFar, maxEndingHere)
      • 如果当前位置结束的子数组和大于全局最大和,则更新全局最大和。
  3. 返回结果

    • 最终,maxSoFar 存储的即为最大子数组和。

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组 nums 的长度。我们只需要遍历一次数组。
  • 空间复杂度:O(1),只需常数空间存储几个变量。

算法图解

假设我们有如下数组:

image.png

nums = [-2, 1, -3, 4, -1, 2, 1, -5, -2, 5]

我们将按照 Kadane's 算法来计算这个数组的最大子数组和。

初始化:

  • maxEndingHere = -2(数组的第一个元素)
  • maxSoFar = -2(全局最大和,初始为第一个元素)

迭代过程:

  1. i = 1

    • maxEndingHere = max(1, -2 + 1) = 1
    • maxSoFar = max(-2, 1) = 1
    • 当前最大子数组和:1
  2. i = 2

    • maxEndingHere = max(-3, 1 - 3) = -2
    • maxSoFar = max(1, -2) = 1
    • 当前最大子数组和:1
  3. i = 3

    • maxEndingHere = max(4, -2 + 4) = 4
    • maxSoFar = max(1, 4) = 4
    • 当前最大子数组和:4
  4. i = 4

    • maxEndingHere = max(-1, 4 - 1) = 3
    • maxSoFar = max(4, 3) = 4
    • 当前最大子数组和:4
  5. i = 5

    • maxEndingHere = max(2, 3 + 2) = 5
    • maxSoFar = max(4, 5) = 5
    • 当前最大子数组和:5
  6. i = 6

    • maxEndingHere = max(1, 5 + 1) = 6
    • maxSoFar = max(5, 6) = 6
    • 当前最大子数组和:6
  7. i = 7

    • maxEndingHere = max(-5, 6 - 5) = 1
    • maxSoFar = max(6, 1) = 6
    • 当前最大子数组和:6
  8. i = 8

    • maxEndingHere = max(-2, 1 - 2) = -1
    • maxSoFar = max(6, -1) = 6
    • 当前最大子数组和:6
  9. i = 9

    • maxEndingHere = max(5, -1 + 5) = 5
    • maxSoFar = max(6, 5) = 6
    • 当前最大子数组和:6

结果:

最终的全局最大子数组和是 6,对应的最大子数组为 [4, -1, 2, 1],这部分子数组的和是 6

算法题—翻转增益的最大子数组和

问题描述

小C面对一个由整数构成的数组,他考虑通过一次操作提升数组的潜力。这个操作允许他选择数组中的任一子数组并将其翻转,目的是在翻转后的数组中找到具有最大和的子数组。小C对这个可能性很感兴趣,并希望知道翻转后的数组中可能得到的最大子数组和是多少。

例如,数组是 1, 2, 3, -1, 4。小C可以选择翻转子数组 -1, 4 得到 1, 2, 3, 4, -1 或者翻转 1, 2, 3, -1 得到 -1, 3, 2, 1, 4,在这两种情况下,最大的子数组和都是 10。

备注:子数组 是数组中的一个连续部分。

输入

  • N: 数组的长度
  • data_array: 一个长度为 N 的整数数组

输出

请你返回执行翻转操作后(也可以选择不翻转),数组中可能获得的最大子数组和。


测试样例

样例1:

输入:N = 5,data_array = [1, 2, 3, -1, 4] 输出:10

样例2:

输入:N = 4,data_array = [-3, -1, -2, 3] 输出:3

样例3:

输入:N = 3,data_array = [-1, -2, -3] 输出:-1

样例4:

输入:N = 6,data_array = [-5, -9, 6, 7, -6, 2] 输出:15

样例5:

输入:N = 7,data_array = [-8, -1, -2, -3, 4, -5, 6] 输出:10

解题

def solution(N, data_array):
    # 计算原始数组的最大子数组和
    max_sum = kadane(data_array)
    
    # 考虑翻转操作的影响
    for i in range(N):
        for j in range(i, N):
            # 翻转子数组 data_array[i:j+1]
            reversed_subarray = data_array[i:j+1][::-1]
            # 计算翻转后的数组的最大子数组和
            max_sum = max(max_sum, kadane(data_array[:i] + reversed_subarray + data_array[j+1:]))
    
    return max_sum

def kadane(array):
    # Kadane算法实现
    max_ending_here = max_so_far = array[0]
    for x in array[1:]:
        max_ending_here = max(x, max_ending_here + x)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

if __name__ == "__main__":
    array1 = [-85, -11, 92, 6, 49, -76, 28, -16, 3, -29, 26, 37, 86, 3, 25, -43, -36, -27, 45, 87, 91, 58, -15, 91, 5, 99, 40, 68, 54, -95, 66, 49, 74, 9, 24, -84, 12, -23, -92, -47, 5, 91, -79, 94, 61, -54, -71, -36, 31, 97, 64, -14, -16, 48, -79, -70, 54, -94, 48, 37, 47, -58, 6, 62, 19, 8, 32, 65, -81, -27, 14, -18, -34, -64, -97, -21, -76, 51, 0, -79, -22, -78, -95, -90, 4, 82, -79, -85, -64, -79, 63, 49, 21, 97, 47, 16, 61, -46, 54, 44]
    print(solution(5, [1,2,3,-1,4]) == 10 )
    print(solution(5, [-1,-2,-3,-4,-5]) == -1 )
    print(solution(100, array1) == 1348)