200字
天然蓄水库
2025-12-05
2025-12-14

题目描述

公元2919年,人类终于发现了一颗宜居星球——X星。 现想在X星一片连绵起伏的山脉间建一个天热蓄水库,如何选取水库边界,使蓄水量最大?

要求:

  • 山脉用正整数数组s表示,每个元素代表山脉的高度。

  • 选取山脉上两个点作为蓄水库的边界,则边界内的区域可以蓄水,蓄水量需排除山脉占用的空间

  • 蓄水量的高度为两边界的最小值。

  • 如果出现多个满足条件的边界,应选取距离最近的一组边界。

输出边界下标(从0开始)和最大蓄水量;如果无法蓄水,则返回0,此时不返回边界。 例如,当山脉为s=[3,1,2]时,则选取s[0]和s[2]作为水库边界,则蓄水量为1,此时输出:0 2:1 当山脉s=[3,2,1]时,不存在合理的边界,此时输出:0。

输入描述

一行正整数,用空格隔开,例如输入

1 2 3

表示s=[1,2,3]

输出描述

当存在合理的水库边界时,输出左边界、空格、右边界、英文冒号、蓄水量;例如

0 2:1

当不存在合理的书库边界时,输出0;例如

0

备注

  • 1 ≤ length(s) ≤ 10000

  • 0 ≤ s[i] ≤ 10000

示例1

输入

1 9 6 2 5 4 9 3 7

输出

1 6:19

说明

经过分析,选取s[1]和s[6],水库蓄水量为19(3+7+4+5)

示例2

输入

1 8 6 2 5 4 8 3 7

输出

1 6:15

说明

经过分析,选取s[1]和s[8]时,水库蓄水量为15;同样选取s[1]和s[6]时,水库蓄水量也为15。由于后者下标距离小(为5),故应选取后者。

def prefix_sum(height):
    n = len(height)
    prefix = [0] * (n + 1)
    for i in range(1, n + 1):
        prefix[i] = prefix[i - 1] + height[i - 1]
    return prefix


def compare_water(height, prefix, left, right):
    if right - left <= 1:
        return 0
    mid_area = prefix[right] - prefix[left + 1]
    all_area = min(height[left], height[right]) * (right - left - 1)
    water = all_area - mid_area
    return max(water, 0)


def find_data(height):
    if len(height) < 3:
        return "-1 -1:0"

    prefix = prefix_sum(height)
    max_water = 0
    min_distance = float('inf')
    res_left = -1
    res_right = -1

    left = 0
    right = len(height) - 1

    while left < right:
        water = compare_water(height, prefix, left, right)

        # 更新最大储水量
        if water > max_water:
            max_water = water
            min_distance = right - left
            res_left, res_right = left, right
        elif water == max_water and water > 0:
            # 储水量相同时选择距离更近的
            if right - left < min_distance:
                min_distance = right - left
                res_left, res_right = left, right

        # 移动指针
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1

    return f"{res_left} {res_right}:{max_water}" if max_water > 0 else "-1 -1:0"

def main():
    import sys
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        height = list(map(int, line.split()))
        print(find_data(height))
if __name__ == '__main__':
    main()

这个算法的核心思想是双指针法解决最大储水容器问题,但有一些特殊的设计考虑:

核心算法思想

1. 双指针遍历策略

  • 使用两个指针 leftright 分别从数组的两端开始

  • 每次比较两端的柱子高度,移动较矮的一侧(贪心策略)

  • 原因是:较矮的柱子限制了储水量,移动它可能找到更高的柱子,从而增加储水潜力

2. 储水量计算逻辑

储水量 = 容器的总容量 - 中间柱子的体积

  • 容器总容量min(height[left], height[right]) × (right - left - 1)

    • 以较矮的柱子为基准水位

    • 宽度是左右柱子间的距离减1(因为左右柱子本身不储水)

  • 中间柱子体积:使用前缀和快速计算 prefix[right] - prefix[left + 1]

  • 实际储水量总容量 - 中间体积

3. 特殊优化目标

这个算法不仅要找到最大储水量,还要考虑:

  1. 优先最大储水量

  2. 储水量相同时,选择距离最近的一对柱子

    • 通过 min_distance 变量跟踪

    • 距离定义为 right - left

4. 前缀和优化

  • 预处理前缀和数组 prefix

  • 使计算中间柱子体积的时间复杂度从 O(n) 降到 O(1)

  • prefix[i] 表示前 i 个柱子的高度总和

时间复杂度分析

  • O(n):双指针从两端向中间移动,总共最多 n-1 次移动

  • O(1):每次计算储水量只需要常数时间(借助前缀和)

  • 空间复杂度 O(n):存储前缀和数组

算法正确性保证

为什么移动较矮的柱子是正确的?

  1. 当前容器容量受限于较矮的柱子

  2. 移动较高的柱子不会增加容量上限,因为上限由较矮的柱子决定

  3. 移动较矮的柱子可能找到更高的替代,从而可能增加容量

与经典"接雨水"问题的区别

经典接雨水问题(LeetCode 42):

  • 计算所有柱子能接的总雨水量

  • 每个位置的水位由左右两边最高柱子中的较矮者决定

这个算法的问题:

  • 只选择两个柱子作为容器的边界

  • 计算这两个柱子之间的储水量

  • 寻找储水量最大的一对柱子

应用场景

这种算法适用于:

  • 选择水库的两个坝址,使蓄水量最大

  • 建筑设计中选择两个支撑点

  • 任何需要选择两个边界来最大化中间区域容积的问题

这个算法的优雅之处在于:

  1. 简单直观:用双指针扫描一遍即可

  2. 高效:O(n)时间复杂度

  3. 灵活:可以在寻找最大储水量的同时考虑其他因素(如距离)

天然蓄水库
作者
Shisuiyi
发表于
2025-12-05
License
CC BY-NC-SA 4.0

评论