华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
2025 A卷 200分 题型
本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析;
并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式!
2025华为OD真题目录+全流程解析/备考攻略/经验分享
华为OD机试真题《天然蓄水库》:
目录
- 题目名称:天然蓄水库
- 题目描述
- 示例
- Java
- 题目分析
- 解决思路
- Java 代码实现
- 代码详细解析
- 综合分析
- python
- 题目分析
- 解决思路
- Python 代码实现
- 代码详细解析
- 关键逻辑图解
- 综合分析
- JavaScript
- 题目分析
- JavaScript 代码实现
- 代码详细解析
- 综合分析
- 示例测试
- 示例1:
- 示例2:
- 总结
- C++
- 解决思路
- 代码实现
- 关键步骤解析
- 正确性验证
- C语言
- 解决思路
- 代码实现
- 代码详细解析
- 复杂度与优化
- 示例测试
- GO
- 题目分析
- Go 代码实现
- 代码详细解析
- 示例验证
- 输入1:
- 输出1:
- 时间复杂度与优化
- 边界处理
- 综合分析
- 更多内容:
题目名称:天然蓄水库
- 知识点:双指针
- 时间限制:1秒
- 空间限制:256MB
- 限定语言:不限
题目描述
公元2919年,人类在X星山脉间建造天然蓄水库,需选取两边界使蓄水量最大。要求:
- 山脉用数组
s
表示,元素为高度。 - 边界内的蓄水量为两边界高度的最小值减去中间山脉占用的空间。
- 若有多个解,选下标距离最近的边界。
- 无法蓄水则输出
0
,否则输出左、右边界及蓄水量。
输入描述
一行正整数(空格分隔),如 1 9 6 2 5 4 9 3 7
,表示s = [1,9,6,2,5,4,9,3,7]
。
输出描述
若存在合理边界,输出格式为左边界 右边界:蓄水量
,如1 6:19
;否则输出0
。
示例
示例1
输入:
1 9 6 2 5 4 9 3 7
输出:
1 6:19
说明:选s[1]
(高9)和s[6]
(高9),中间蓄水量为3+7+4+5=19
。
示例2
输入:
3 2 1
输出:
0
补充说明
- 数组长度范围:
1 <= length(s) <= 10000
- 山脉高度范围:
0 <= s[i] <= 10000
Java
题目分析
题目要求在给定的山脉数组中选择两个边界,使得它们之间的蓄水量最大。蓄水量计算公式为:两边界高度的最小值 × 区间长度 - 中间山脉的总高度。需要处理以下关键点:
- 高效计算:避免暴力枚举所有可能的左右边界(O(n²) 时间复杂度),采用双指针法实现 O(n) 时间复杂度。
- 边界条件:处理无法蓄水的情况(结果 ≤ 0)和多个解时选择下标距离最近的边界。
- 数据预处理:使用前缀和数组快速计算中间山脉的总高度。
解决思路
- 双指针法:初始化左右指针分别指向数组首尾,每次移动较小高度的指针,保留较大高度的边界以探索更大蓄水量。
- 前缀和优化:预处理前缀和数组,实现区间和 O(1) 查询。
- 条件判断:维护最大蓄水量及对应的左右边界,处理距离更短的解。
Java 代码实现
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);String[] parts = scanner.nextLine().trim().split(" ");int n = parts.length;int[] s = new int[n];for (int i = 0; i < n; i++) {s[i] = Integer.parseInt(parts[i]);}if (n < 2) {System.out.println(0);return;}// 预处理前缀和数组,preSum[i] 表示前i个元素的总和int[] preSum = new int[n + 1];for (int i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + s[i];}int left = 0, right = n - 1;int maxWater = 0;int bestLeft = -1, bestRight = -1;while (left < right) {int minH = Math.min(s[left], s[right]);int count = right - left - 1;if (count > 0) { // 仅当区间内有元素时计算int sumMiddle = preSum[right] - preSum[left + 1];int water = minH * count - sumMiddle;// 更新最大值或更优解(距离更近)if (water > maxWater) {maxWater = water;bestLeft = left;bestRight = right;} else if (water == maxWater) {int currentDistance = right - left;int bestDistance = bestRight - bestLeft;if (currentDistance < bestDistance || bestDistance == -1) {bestLeft = left;bestRight = right;}}}// 移动较小高度的指针,保留较高边界if (s[left] < s[right]) {left++;} else {right--;}}// 输出结果if (maxWater <= 0) {System.out.println(0);} else {System.out.println(bestLeft + " " + bestRight + ":" + maxWater);}}
}
代码详细解析
-
输入处理:
- 读取输入并转换为整数数组
s
。 - 处理长度为 1 的特殊情况(无法形成蓄水区间)。
- 读取输入并转换为整数数组
-
前缀和数组:
preSum[i]
表示前i
个元素的总和,用于快速计算任意区间和。
-
双指针遍历:
left
和right
初始指向数组两端。minH
计算当前左右边界的最小高度。count
表示中间山脉的数量,sumMiddle
通过前缀和数组快速求得。
-
蓄水量计算:
water = minH * count - sumMiddle
直接计算当前区间的蓄水量。- 维护
maxWater
和对应的最优边界bestLeft
和bestRight
。
-
指针移动策略:
- 移动较小高度的指针,保留较高的边界以探索更大的蓄水量。
-
结果输出:
- 根据
maxWater
决定输出格式,处理无效解(结果 ≤ 0)。
- 根据
综合分析
- 时间复杂度:双指针法将时间复杂度从 O(n²) 优化到 O(n),适用于大规模数据(n ≤ 10000)。
- 空间复杂度:仅需 O(n) 空间存储前缀和数组。
- 正确性保障:
- 前缀和数组确保区间和计算的快速和准确。
- 指针移动策略保留较高边界,最大化后续探索的潜力。
- 处理多个解时,优先选择下标距离更近的边界。
python
题目分析
题目要求在给定的山脉数组中选择两个边界,使得它们之间的蓄水量最大。蓄水量计算公式为:两边界高度的最小值 × 区间长度 - 中间山脉的总高度。需要处理以下关键点:
- 高效计算:避免暴力枚举所有可能的左右边界(O(n²) 时间复杂度),采用双指针法实现 O(n) 时间复杂度。
- 边界条件:处理无法蓄水的情况(结果 ≤ 0)和多个解时选择下标距离最近的边界。
- 数据预处理:使用前缀和数组快速计算中间山脉的总高度。
解决思路
- 双指针法:初始化左右指针分别指向数组首尾,每次移动较小高度的指针,保留较大高度的边界以探索更大蓄水量。
- 前缀和优化:预处理前缀和数组,实现区间和 O(1) 查询。
- 条件判断:维护最大蓄水量及对应的左右边界,处理距离更短的解。
Python 代码实现
def main():# 读取输入并转换为整数数组s = list(map(int, input().strip().split()))n = len(s)# 处理特殊情况:数组长度不足时无法形成蓄水区间if n < 2:print(0)return# 构建前缀和数组 pre_sum,pre_sum[i] 表示前i个元素的总和pre_sum = [0] * (n + 1)for i in range(n):pre_sum[i + 1] = pre_sum[i] + s[i]# 初始化双指针和最大值变量left, right = 0, n - 1max_water = 0best_left, best_right = -1, -1while left < right:# 当前左右边界的高度min_h = min(s[left], s[right])# 中间山脉的数量(区间长度为 right - left - 1)count = right - left - 1if count > 0: # 仅当区间内有元素时计算蓄水量# 计算中间山脉的总高度:前缀和差法sum_middle = pre_sum[right] - pre_sum[left + 1]# 当前蓄水量 = 容器高度 × 区间长度 - 中间总高度current_water = min_h * count - sum_middle# 如果当前蓄水量更大,更新最大值和最优边界if current_water > max_water:max_water = current_waterbest_left, best_right = left, right# 如果蓄水量相同,但区间更短,则更新最优边界elif current_water == max_water:current_distance = right - leftbest_distance = best_right - best_leftif current_distance < best_distance or best_distance == -1:best_left, best_right = left, right# 移动指针策略:保留较高的边界,移动较矮的一边if s[left] < s[right]:left += 1else:right -= 1# 输出结果if max_water <= 0:print(0)else:# 注意输出的是原数组下标,不需要调整print(f"{best_left} {best_right}:{max_water}")if __name__ == "__main__":main()
代码详细解析
def main():# 读取输入并转换为整数数组s = list(map(int, input().strip().split())) # 例如输入 "1 9 6 2 5" → [1,9,6,2,5]n = len(s) # 数组长度# 处理特殊情况:数组长度不足时无法形成蓄水区间if n < 2:print(0) # 直接输出0return # 结束函数# 构建前缀和数组 pre_sum,pre_sum[i] 表示前i个元素的总和pre_sum = [0] * (n + 1) # 初始化长度为n+1的数组,初始值全0for i in range(n): # 遍历原数组pre_sum[i + 1] = pre_sum[i] + s[i] # 前i+1项和 = 前i项和 + 当前项# 初始化双指针和最大值变量left, right = 0, n - 1 # 左右指针初始指向数组首尾max_water = 0 # 最大蓄水量best_left, best_right = -1, -1 # 最优解的左右边界下标while left < right: # 双指针未相遇时循环# 当前左右边界的高度min_h = min(s[left], s[right]) # 容器高度由较矮的边界决定# 中间山脉的数量(区间长度为 right - left - 1)count = right - left - 1 # 例如区间 [2,5] 中间有元素3、4,数量为2if count > 0: # 仅当区间内有元素时计算蓄水量# 计算中间山脉的总高度:前缀和差法sum_middle = pre_sum[right] - pre_sum[left + 1] # [left+1, right-1]区间和# 当前蓄水量 = 容器高度 × 区间长度 - 中间总高度current_water = min_h * count - sum_middle# 如果当前蓄水量更大,更新最大值和最优边界if current_water > max_water:max_water = current_waterbest_left, best_right = left, right# 如果蓄水量相同,但区间更短,则更新最优边界elif current_water == max_water:current_distance = right - left # 当前区间距离best_distance = best_right - best_left # 历史最优区间距离# 如果当前区间更短,或者尚未记录最优解(初始为-1)if current_distance < best_distance or best_distance == -1:best_left, best_right = left, right# 移动指针策略:保留较高的边界,移动较矮的一边if s[left] < s[right]: # 左边界较矮left += 1 # 左指针右移,尝试寻找更高的左边界else: # 右边界较矮或相等right -= 1 # 右指针左移,尝试寻找更高的右边界# 输出结果if max_water <= 0: # 无效蓄水(可能所有区间计算值≤0)print(0)else:# 注意输出的是原数组下标,不需要调整(Python列表索引从0开始)print(f"{best_left} {best_right}:{max_water}")
关键逻辑图解
假设输入为 s = [1, 9, 6, 2, 5, 4, 9, 3, 7]
,最优解为 s[1]
和 s[6]
:
-
初始化指针:
left=0
,right=8
(对应高度1和7)- 计算
min_h = min(1,7) = 1
- 中间元素数量
count = 8-0-1 = 7
- 中间总和
sum_middle = sum(s[1]~s[7]) = 9+6+2+5+4+9+3 = 38
- 蓄水量
1*7 - 38 = -31
(无效)
- 计算
-
移动左指针(因为左边界较矮)→
left=1
(高度9)- 现在
left=1
,right=8
(高度9和7) min_h = min(9,7) = 7
- 中间元素数量
count = 8-1-1 = 6
- 中间总和
sum(s[2]~s[7]) = 6+2+5+4+9+3 = 29
- 蓄水量
7*6 -29 = 42-29=13
(有效)
- 现在
-
继续移动右指针(右边界较矮)→ 逐步收敛,直到找到最优解。
综合分析
- 时间复杂度 O(n):双指针仅遍历一次数组。
- 空间复杂度 O(n):仅需存储前缀和数组。
- 处理多个解的正确性:当蓄水量相同时,优先选择下标距离更近的边界。
- 健壮性:处理无效输入和边界条件(如数组长度为1)。
JavaScript
题目分析
题目要求在给定的山脉数组中选择两个边界,使得它们之间的蓄水量最大。蓄水量计算公式为:两边界高度的最小值 × 区间长度 - 中间山脉的总高度。需要处理以下关键点:
- 高效计算:使用双指针法实现 O(n) 时间复杂度。
- 边界条件:处理无法蓄水的情况(结果 ≤ 0)和多个解时选择下标距离最近的边界。
- 数据预处理:使用前缀和数组快速计算中间山脉的总高度。
JavaScript 代码实现
const readline = require('readline');const rl = readline.createInterface({input: process.stdin,output: process.stdout
});rl.on('line', (input) => {const s = input.trim().split(/\s+/).map(Number);const n = s.length;if (n < 2) {console.log(0);rl.close();return;}// 构建前缀和数组 preSum,preSum[i] 表示前i个元素的总和const preSum = new Array(n + 1).fill(0);for (let i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + s[i];}let left = 0, right = n - 1;let maxWater = 0;let bestLeft = -1, bestRight = -1;while (left < right) {const minH = Math.min(s[left], s[right]);const count = right - left - 1;if (count > 0) {const sumMiddle = preSum[right] - preSum[left + 1];const currentWater = minH * count - sumMiddle;if (currentWater > maxWater) {maxWater = currentWater;bestLeft = left;bestRight = right;} else if (currentWater === maxWater) {const currentDistance = right - left;const bestDistance = bestRight - bestLeft;if (currentDistance < bestDistance || bestDistance === -1) {bestLeft = left;bestRight = right;}}}// 移动较小高度的指针if (s[left] < s[right]) {left++;} else {right--;}}if (maxWater <= 0) {console.log(0);} else {console.log(`${bestLeft} ${bestRight}:${maxWater}`);}rl.close();
});
代码详细解析
-
输入处理:
const s = input.trim().split(/\s+/).map(Number);
- 读取输入行,去除首尾空格,按空格分割为字符串数组,并转换为数字数组。
-
特殊情况处理:
if (n < 2) {console.log(0);return; }
- 数组长度小于2时无法形成蓄水区间,直接输出0。
-
前缀和数组:
const preSum = new Array(n + 1).fill(0); for (let i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + s[i]; }
preSum[i]
存储前i
个元素的总和,用于快速计算任意区间和。
-
双指针初始化:
let left = 0, right = n - 1; let maxWater = 0; let bestLeft = -1, bestRight = -1;
left
和right
初始指向数组两端,maxWater
记录最大蓄水量,bestLeft
和bestRight
记录最优边界。
-
双指针遍历:
while (left < right) {const minH = Math.min(s[left], s[right]);const count = right - left - 1;if (count > 0) {const sumMiddle = preSum[right] - preSum[left + 1];const currentWater = minH * count - sumMiddle;// 更新最大值或更优解}// 移动指针 }
- 每次循环计算当前边界的蓄水量,并更新最优解。
-
蓄水量计算:
const sumMiddle = preSum[right] - preSum[left + 1]; const currentWater = minH * count - sumMiddle;
sumMiddle
是中间山脉的总高度,currentWater
是当前蓄水量。
-
更新最优解:
if (currentWater > maxWater) {maxWater = currentWater;bestLeft = left;bestRight = right; } else if (currentWater === maxWater) {// 处理距离更近的情况 }
- 当蓄水量相同时,优先选择下标距离更近的边界。
-
移动指针策略:
if (s[left] < s[right]) {left++; } else {right--; }
- 移动较小高度的指针,保留较高边界以探索更大蓄水量。
-
输出结果:
if (maxWater <= 0) {console.log(0); } else {console.log(`${bestLeft} ${bestRight}:${maxWater}`); }
- 根据最大蓄水量决定输出格式。
综合分析
- 时间复杂度 O(n):双指针法确保每个元素仅被访问一次。
- 空间复杂度 O(n):仅需存储前缀和数组,适用于大规模数据。
- 健壮性:
- 前缀和数组避免重复计算区间和。
- 处理输入格式和边界条件(如空输入、数组长度为1)。
- 正确性:
- 当多个解蓄水量相同时,选择下标距离最近的边界。
- 严格处理无效蓄水(结果 ≤ 0 时输出0)。
示例测试
示例1:
输入:1 9 6 2 5 4 9 3 7
输出:1 6:19
- 左边界为
s[1]=9
,右边界为s[6]=9
,中间总高度为6+2+5+4=17
,蓄水量为9*4 -17=19
。
示例2:
输入:3 2 1
输出:0
- 任何两边界蓄水量均为负或零。
总结
通过双指针法和前缀和数组的结合,JavaScript 实现能够高效、准确地求解最大蓄水量问题。代码逻辑清晰,充分考虑了各种边界条件,是解决此类问题的最佳方案。
C++
解决思路
本问题要求在给定山脉数组中,选择两边界使蓄水量最大。蓄水量计算公式为:两边界高度的最小值 × 中间元素数量 - 中间山脉总高度。使用双指针法高效遍历可能解,结合前缀和数组快速计算区间和,确保时间复杂度为 O(n)。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>using namespace std;int main() {string line;getline(cin, line); // 读取整行输入istringstream iss(line); // 创建字符串流用于分割输入vector<int> s; // 存储山脉高度的数组int num;while (iss >> num) { // 解析每个整数s.push_back(num);}int n = s.size();if (n < 2) { // 处理长度不足的情况cout << 0 << endl;return 0;}// 构建前缀和数组,pre_sum[i]表示前i个元素的总和vector<int> pre_sum(n + 1, 0);for (int i = 0; i < n; ++i) {pre_sum[i + 1] = pre_sum[i] + s[i];}int left = 0, right = n - 1; // 双指针初始化int max_water = 0; // 最大蓄水量int best_left = -1, best_right = -1; // 最优解的左右边界while (left < right) {int minH = min(s[left], s[right]); // 当前边界的最小高度int count = right - left - 1; // 中间元素的数量if (count > 0) { // 仅当中间有元素时计算蓄水量int sum_middle = pre_sum[right] - pre_sum[left + 1]; // 中间山脉总高度int current_water = minH * count - sum_middle; // 当前蓄水量// 更新最大值或更优解(距离更近)if (current_water > max_water) {max_water = current_water;best_left = left;best_right = right;} else if (current_water == max_water) {int current_dist = right - left; // 当前区间距离int best_dist = best_right - best_left; // 历史最优区间距离if (best_left == -1 || current_dist < best_dist) {best_left = left;best_right = right;}}}// 移动较小高度的指针,保留较高边界以探索更大蓄水量if (s[left] < s[right]) {++left;} else {--right;}}// 输出结果if (max_water <= 0) {cout << 0 << endl;} else {cout << best_left << " " << best_right << ":" << max_water << endl;}return 0;
}
关键步骤解析
- 输入处理:读取输入行并解析为整数数组。
- 前缀和数组:预处理前缀和数组
pre_sum
,用于快速计算任意区间和。 - 双指针遍历:
- 初始化左右指针
left
和right
,分别指向数组首尾。 - 计算当前边界的较小高度
minH
和中间元素数量count
。 - 利用前缀和数组计算中间山脉总高度
sum_middle
,进而得到当前蓄水量current_water
。
- 初始化左右指针
- 更新最优解:维护最大蓄水量
max_water
及对应的边界,确保在蓄水量相同时选择距离更近的边界。 - 指针移动策略:移动较小高度的指针,保留较高边界以探索更大的蓄水量。
- 结果输出:根据最大蓄水量决定输出格式,处理无效解。
正确性验证
通过双指针法和前缀和数组的结合,确保在 O(n) 时间复杂度内找到最优解。双指针策略保留较高边界,确保不会错过潜在的最大蓄水量组合。前缀和数组优化区间和计算,保证效率。示例测试验证代码正确性,覆盖各种边界条件。
C语言
解决思路
题目要求在给定山脉数组中找出两个边界,使蓄水量最大。蓄水量计算公式为:两边界高度最小值 × 中间元素数量 - 中间山脉总高度
。采用双指针法遍历所有可能的边界组合,结合前缀和数组优化计算效率。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>#define INITIAL_CAPACITY 10int main() {char line[100000]; // 输入缓冲区fgets(line, sizeof(line), stdin); // 读取输入行// 动态数组存储山脉高度int *s = (int *)malloc(INITIAL_CAPACITY * sizeof(int));int capacity = INITIAL_CAPACITY;int n = 0; // 数组长度// 分割输入字符串为整数数组char *token = strtok(line, " \n");while (token != NULL) {// 动态扩容if (n >= capacity) {capacity *= 2;s = (int *)realloc(s, capacity * sizeof(int));}s[n++] = atoi(token);token = strtok(NULL, " \n");}// 处理长度不足的情况if (n < 2) {printf("0\n");free(s);return 0;}// 构建前缀和数组int *pre_sum = (int *)malloc((n + 1) * sizeof(int));pre_sum[0] = 0;for (int i = 0; i < n; i++) {pre_sum[i + 1] = pre_sum[i] + s[i];}// 双指针初始化int left = 0, right = n - 1;int max_water = 0;int best_left = -1, best_right = -1;// 双指针遍历逻辑while (left < right) {int minH = s[left] < s[right] ? s[left] : s[right];int count = right - left - 1;if (count > 0) {int sum_middle = pre_sum[right] - pre_sum[left + 1];int current_water = minH * count - sum_middle;// 更新最大值或更优解if (current_water > max_water) {max_water = current_water;best_left = left;best_right = right;} else if (current_water == max_water) {int current_dist = right - left;int best_dist = best_right - best_left;if (best_left == -1 || current_dist < best_dist) {best_left = left;best_right = right;}}}// 移动较小高度的指针if (s[left] < s[right]) {left++;} else {right--;}}// 输出结果if (max_water <= 0) {printf("0\n");} else {printf("%d %d:%d\n", best_left, best_right, max_water);}// 释放动态内存free(s);free(pre_sum);return 0;
}
代码详细解析
-
输入处理
char line[100000]; fgets(line, sizeof(line), stdin);
- 创建缓冲区读取整行输入,支持最长 100,000 字符。
-
动态数组初始化
int *s = (int *)malloc(INITIAL_CAPACITY * sizeof(int)); int capacity = INITIAL_CAPACITY;
- 初始分配容量为 10 的数组,避免频繁扩容。
-
分割字符串为整数
char *token = strtok(line, " \n"); while (token != NULL) {// 动态扩容if (n >= capacity) {capacity *= 2;s = realloc(s, capacity * sizeof(int));}s[n++] = atoi(token);token = strtok(NULL, " \n"); }
- 使用
strtok
按空格和换行符分割字符串。 - 动态扩容策略:容量不足时翻倍,保证插入效率。
- 使用
-
前缀和数组构建
int *pre_sum = malloc((n + 1) * sizeof(int)); pre_sum[0] = 0; for (int i = 0; i < n; i++) {pre_sum[i + 1] = pre_sum[i] + s[i]; }
pre_sum[i]
存储前i
个元素的和,快速计算任意区间和。
-
双指针核心逻辑
while (left < right) {int minH = s[left] < s[right] ? s[left] : s[right];int count = right - left - 1;if (count > 0) {int sum_middle = pre_sum[right] - pre_sum[left + 1];int current_water = minH * count - sum_middle;// 更新最大值或更优解if (...) { ... }}// 移动指针if (...) { ... } }
- 每次循环计算当前边界的蓄水量。
- 移动较小高度的指针,保留较大边界以探索更大蓄水量。
-
释放内存
free(s); free(pre_sum);
- 避免内存泄漏,确保程序健壮性。
复杂度与优化
- 时间复杂度:O(n),双指针遍历数组一次。
- 空间复杂度:O(n),用于存储山脉数组和前缀和数组。
- 优势:通过双指针法和前缀和数组的结合,在保证效率的同时代码简洁清晰。
示例测试
-
输入1:
1 9 6 2 5 4 9 3 7
- 输出:
1 6:19
- 解释:选择
s[1]
(高9)和s[6]
(高9),中间山脉总高度为 6+2+5+4=17,蓄水量为 9×4 -17=19。
- 输出:
-
输入2:
3 2 1
- 输出:
0
- 解释:所有可能的边界组合蓄水量 ≤0。
- 输出:
GO
题目分析
题目要求在给定山脉数组中找到两边界,使得蓄水量最大。蓄水量计算公式为:两边界高度的最小值 × 区间长度 - 中间山脉总高度。通过双指针法高效遍历所有可能的边界组合,确保时间复杂度为 O(n)。
Go 代码实现
package mainimport ("bufio""fmt""os""strconv""strings"
)func main() {// 读取输入行并分割为字符串数组scanner := bufio.NewScanner(os.Stdin)scanner.Scan()input := strings.TrimSpace(scanner.Text())strArr := strings.Fields(input)// 转换为整数数组var s []intfor _, str := range strArr {num, _ := strconv.Atoi(str)s = append(s, num)}n := len(s)// 处理数组长度不足的情况if n < 2 {fmt.Println(0)return}// 构建前缀和数组 preSumpreSum := make([]int, n+1)for i := 0; i < n; i++ {preSum[i+1] = preSum[i] + s[i]}// 双指针初始化left, right := 0, n-1maxWater := 0bestLeft, bestRight := -1, -1// 双指针遍历逻辑for left < right {minH := min(s[left], s[right])count := right - left - 1if count > 0 {sumMiddle := preSum[right] - preSum[left+1]currentWater := minH*count - sumMiddle// 更新最大值或更优解if currentWater > maxWater {maxWater = currentWaterbestLeft, bestRight = left, right} else if currentWater == maxWater {currentDist := right - leftbestDist := bestRight - bestLeftif bestLeft == -1 || currentDist < bestDist {bestLeft, bestRight = left, right}}}// 移动较小高度的指针if s[left] < s[right] {left++} else {right--}}// 输出结果if maxWater <= 0 {fmt.Println(0)} else {fmt.Printf("%d %d:%d\n", bestLeft, bestRight, maxWater)}
}// 辅助函数:获取两个整数的最小值
func min(a, b int) int {if a < b {return a}return b
}
代码详细解析
// 读取输入
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
input := strings.TrimSpace(scanner.Text())
bufio.Scanner
读取标准输入的一行数据,TrimSpace
去除首尾空格。
// 转换为整数数组
strArr := strings.Fields(input)
var s []int
for _, str := range strArr {num, _ := strconv.Atoi(str)s = append(s, num)
}
strings.Fields
按空格分割字符串为数组。strconv.Atoi
将字符串转为整数,并存入动态切片s
。
// 前缀和数组
preSum := make([]int, n+1)
for i := 0; i < n; i++ {preSum[i+1] = preSum[i] + s[i]
}
preSum[i+1]
存储前i
个元素的总和,例如preSum[3] = s[0]+s[1]+s[2]
。
// 双指针核心逻辑
for left < right {minH := min(s[left], s[right])count := right - left - 1if count > 0 {sumMiddle := preSum[right] - preSum[left+1]currentWater := minH*count - sumMiddle// 更新最优解if currentWater > maxWater {maxWater = currentWaterbestLeft, bestRight = left, right} else if currentWater == maxWater {// 选择下标距离更近的解if right-left < bestRight-bestLeft {bestLeft, bestRight = left, right}}}// 移动指针if s[left] < s[right] {left++} else {right--}
}
- 双指针移动策略:每次移动较矮的边界,保留较高的边界以探索更大的蓄水量。
- 蓄水量计算:
minH * count
是理想容器体积,sumMiddle
是实际山脉体积,差值即蓄水量。
示例验证
输入1:
1 9 6 2 5 4 9 3 7
- 解析:
- 左右指针初始指向 0 和 8(值 1 和 7)。
- 逐步移动左指针到 1(值 9),右指针到 6(值 9)。
- 中间山脉总高度 = 6 + 2 + 5 + 4 = 17。
- 蓄水量 = 9×4 - 17 = 19。
输出1:
1 6:19
时间复杂度与优化
- 时间复杂度:O(n),双指针遍历数组一次。
- 空间复杂度:O(n),用于存储前缀和数组。
- 优势:
- 双指针法避免暴力枚举所有 O(n²) 组合。
- 前缀和数组实现区间和 O(1) 查询。
边界处理
- 数组长度 < 2:直接返回 0。
- 所有蓄水量 ≤ 0:输出 0。
- 多解选择:蓄水量相同时优先选择下标距离更近的边界。
综合分析
- 高效性:双指针法在 O(n) 时间内解决问题。
- 代码简洁:利用 Go 的切片和内置函数简化输入处理。
- 健壮性:正确处理所有边界条件和多解场景。
- 易读性:通过清晰的变量命名和逻辑分段,代码易于理解和维护。
更多内容:
https://www.kdocs.cn/l/cvk0eoGYucWA
本文发表于【纪元A梦】,关注我,获取更多实用教程/资源!