环形数组中的最大贡献值 | 豆包MarsCode AI 刷题

175 阅读3分钟

问题背景与分析

给定一个长度为 nn 的环形数组 aa,目标是找到一对下标 (i,j)(i, j),使得它们的贡献值 f(i,j)=(ai+aj)×dist(i,j)f(i, j) = (a_i + a_j) \times \text{dist}(i, j) 最大。这里 dist(i,j)\text{dist}(i, j) 表示下标 iijj 之间的最短距离,对于环形数组,最短距离可以通过两种方式计算:

  • 直接距离:dist1=ij\text{dist}_1 = |i - j|
  • 绕过数组末尾的距离:dist2=nij\text{dist}_2 = n - |i - j|

最短距离即为 min(dist1,dist2)\min(\text{dist}_1, \text{dist}_2)

暴力解法

最直接的方法是通过双重循环遍历所有可能的下标对 (i,j)(i, j),计算每一对的贡献值,然后选择贡献值最大的那一对。这种方法的时间复杂度为 O(n2)O(n^2),对于较小的数组是可以接受的,但对于较大的数组可能会非常慢。

def max_contribution_brute_force(n, a):
    max_contrib = 0
    for i in range(n):
        for j in range(n):
            if i != j:
                # 计算最短距离
                dist = min(abs(i - j), n - abs(i - j))
                # 计算贡献值
                contrib = (a[i] + a[j]) * dist
                # 更新最大贡献值
                if contrib > max_contrib:
                    max_contrib = contrib
    return max_contrib

优化解法

为了提高效率,我们可以利用一些数学性质和数据结构来减少不必要的计算。具体来说,可以考虑以下几种方法:

方法一:前缀和与后缀和

我们可以通过预处理前缀和与后缀和来加速计算。具体步骤如下:

  1. 计算每个位置 ii 的前缀和 prefix[i]\text{prefix}[i],表示从数组开头到位置 ii 的元素之和。
  2. 计算每个位置 ii 的后缀和 suffix[i]\text{suffix}[i],表示从位置 ii 到数组末尾的元素之和。
  3. 对于每个位置 ii,计算它与其他位置 jj 的贡献值,利用前缀和与后缀和来快速计算 ai+aja_i + a_j
def max_contribution_optimized(n, a):
    prefix = [0] * n
    suffix = [0] * n
    
    # 计算前缀和
    for i in range(n):
        prefix[i] = sum(a[:i+1])
    
    # 计算后缀和
    for i in range(n-1, -1, -1):
        suffix[i] = sum(a[i:])
    
    max_contrib = 0
    for i in range(n):
        for j in range(n):
            if i != j:
                # 计算最短距离
                dist = min(abs(i - j), n - abs(i - j))
                # 利用前缀和与后缀和计算 a_i + a_j
                ai_plus_aj = (prefix[i] - prefix[i-1] if i > 0 else a[i]) + (suffix[j] - suffix[j+1] if j < n-1 else a[j])
                # 计算贡献值
                contrib = ai_plus_aj * dist
                # 更新最大贡献值
                if contrib > max_contrib:
                    max_contrib = contrib
    return max_contrib

方法二:单调栈

另一种优化方法是使用单调栈来维护一个递增或递减的序列,从而快速找到每个位置 ii 的最优匹配位置 jj。具体步骤如下:

  1. 使用单调栈维护一个递增序列,记录每个位置 ii 及其对应的前缀和。
  2. 对于每个位置 ii,从栈中找到最近的一个比 aia_i 小的位置 jj,计算贡献值并更新最大贡献值。
  3. 类似地,使用另一个单调栈维护一个递减序列,记录每个位置 ii 及其对应的后缀和。
def max_contribution_monotonic_stack(n, a):
    max_contrib = 0
    stack = []
    
    # 处理前缀和
    for i in range(n):
        while stack and a[stack[-1]] < a[i]:
            j = stack.pop()
            dist = min(abs(i - j), n - abs(i - j))
            contrib = (a[i] + a[j]) * dist
            if contrib > max_contrib:
                max_contrib = contrib
        stack.append(i)
    
    # 清空栈
    stack.clear()
    
    # 处理后缀和
    for i in range(n-1, -1, -1):
        while stack and a[stack[-1]] < a[i]:
            j = stack.pop()
            dist = min(abs(i - j), n - abs(i - j))
            contrib = (a[i] + a[j]) * dist
            if contrib > max_contrib:
                max_contrib = contrib
        stack.append(i)
    
    return max_contrib

总结

以上三种方法分别适用于不同的场景:

  • 暴力解法:适用于数组较小的情况,时间复杂度为 O(n2)O(n^2)
  • 前缀和与后缀和:通过预处理前缀和与后缀和来加速计算,时间复杂度仍为 O(n2)O(n^2),但在实际运行中会更快。
  • 单调栈:通过维护单调栈来快速找到最优匹配位置,时间复杂度为 O(n)O(n),适用于较大的数组。

选择合适的方法取决于数组的大小和性能要求。对于大多数实际应用,单调栈方法通常是最优的选择。