📜  最大和双音子数组(1)

📅  最后修改于: 2023-12-03 15:26:25.633000             🧑  作者: Mango

最大和双音子数组介绍

最大和双音子数组(Maximum Sum Biphonic Subarray)是指在一个数列中,包含相邻元素的两个子数组,分别从左往右和从右往左遍历,它们的和之和最大。

这道问题有两种解法:一种是时间复杂度为 O(n^2),另一种是时间复杂度为 O(nlogn)。

O(n^2) 解法

首先,遍历所有的子数组(包括相邻子数组),确定相邻子数组分别从左往右和从右往左遍历。双重循环中,外层循环遍历左边子数组的起点,内层循环遍历右边子数组的起点。根据左右子数组的起点,可以计算出两个子数组的和,将它们相加即可得到这组双音子数组的和。针对所有组双音子数组,取其中和最大的双音子数组即为所求。

这种解法的时间复杂度为 O(n^2),显然随着数组规模的增大,效率会显得特别低下。

def maximum_sum_biphonic_subarray(arr):
    n = len(arr)
    max_sum = float('-inf')
    for i in range(n - 1):
        for j in range(i + 1, n):
            left_sum = sum(arr[i:j + 1])
            right_sum = sum(arr[j:n])
            max_sum = max(max_sum, left_sum + right_sum)
    return max_sum
O(nlogn) 解法

首先,要理解此解法,需要了解一个数学定理:任何一个数列,至少有一个单调不降子序列长度不小于其长度除以2。

具体来说,在一个数列中,将数列分成左右两段,那么必然存在一对相邻的数,其中一个数来自左半边,另一个数来自右半边。这对数的值构成了双音子序列的两个端点。本问题的解法基于这个定理,以及分治的思想。

每次将问题缩小到大小是原问题的一半,然后将左右两段分别求两个单调不降子序列(DP[i]是以arr[i]为结尾的单调不降子序列中最大的和,DP2[i]是以arr[i]为开头的单调不降子列列中最大的和),最后通过线性合并两个DP数组计算出此段范围内的最大双音子序列和。最终,全局最大双音子序列和即为整个数组的最大双音子序列和。

时间复杂度为 O(nlogn)。

import sys

def maximum_sum_biphonic_subarray_2(arr):
    n = len(arr)
    DP = [0] * n
    DP2 = [0] * n
    
    def solve(l, r):
        if l == r:
            return (arr[l], arr[l], arr[l]) # 以arr[l]结尾,以arr[r]开头的DP数组的值都是arr[l]
        mid = (l + r) // 2
        left_dp = solve(l, mid)
        right_dp = solve(mid + 1, r)
        DP[mid], DP2[mid] = arr[mid], arr[mid] # 初始化DP[mid]和DP2[mid]的值
        for i in range(mid - 1, l - 1, -1): # 从mid-1到l
            DP[i] = max(DP[i + 1], 0) + arr[i]
        for i in range(mid + 1, r + 1): # 从mid+1到r
            DP2[i] = max(DP2[i - 1], 0) + arr[i]
        ans = -sys.maxsize # ans用于记录最大双音子序列和
        for i in range(l, mid + 1): # left_dp[0]是以arr[l]结尾的最大单调不降子序列和,DP2[mid + 1]是以arr[mid+1]开头的单调不降子序列的和
            ans = max(ans, left_dp[0] + DP2[mid + 1], DP2[i] + left_dp[2]) # 更新ans
            left_dp = (max(left_dp[0], DP[i]), left_dp[1] + arr[i], max(left_dp[2], left_dp[1] + arr[i])) # 更新left_dp
        right_dp = (right_dp[2], right_dp[1] + arr[mid + 1], max(right_dp[0], right_dp[1] + arr[mid + 1])) # 更新right_dp
        
        for i in range(mid + 2, r + 1): # mid+2是因为mid+1已经算过了
            ans = max(ans, right_dp[0] + DP[i], DP2[mid + 1] + right_dp[2]) # 更新ans
            right_dp = (max(right_dp[0], DP2[i]), right_dp[1] + arr[i], max(right_dp[2], right_dp[1] + arr[i])) # 更新right_dp
        return ans, left_dp[1], right_dp[1]
    return solve(0, n - 1)[0]