题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
关联知识
木桶效应:一只水桶能装多少水取决于它最短的那块木板
此题跟木桶效应类似,当前能接的雨水取决于其**左边最大值与右边最大值中的最小值,也就是所谓的 “短板”
可根据单调性来判断短板的位置,从左到右单调递增时,短板在左边;从左到右单调递减时,短板在右边
解法一:双指针
分别从左右两边往中间遍历,记录当前左边最大值,右边最大值,当左边的当前高度小于等于右边的当前高度时,能接的雨水量为左边最大高度值减去左边当前高度;当左边的当前高度大于右边的当前高度时,能接的雨水量为右边的最大高度值减去右边的当前高度。
int trap(int *height, int heightSize)
{
if (!height || heightSize <= 0) return 0;
int left = 0, right = heightSize - 1;
int leftMax = 0, rightMax = 0, ans = 0; // 最少能接到的雨水是0,所以左边最大值,右边最大值,结果都初始化为0
while (left < right) {
leftMax = fmax(leftMax, height[left]); // 从左边遍历,更新当前左边最大值
rightMax = fmax(rightMax, height[right]); // 从右边遍历,更新当前右边最大值
if (height[left] <= height[right]) {
ans += leftMax - height[left++];
} else {
ans += rightMax - height[right--];
}
}
return ans;
}
解法二:单调栈
从左往右遍历,当前高度要是小于等于前一个高度,就入栈;当前高度要是大于前一个高度,前一个高度能接的雨水就取决于当前高度与前一个高度的前一个高度的最小值,若前一个高度之前没有别的,则不能接到雨水
如 [2, 1, 3],2和3之间能接到雨水,能接到雨水量为 distance(2 - 0 - 1) * height(fmin(2, 3) - 1) = 1 * 1 = 1
如 [1, 3], 虽然3比1大,但是1之前没有别的了,就没办法接到雨水
int trap(int *height, int heightSize)
{
if (!height || heightSize <= 0) return 0;
int *nums = (int *)calloc(heightSize, sizeof(int)); // 用于入栈递减的高度数组下标
int top = 0, curr = 0, ans = 0;
while (curr < heightSize) { // 从左向右遍历
while (top != 0 && height[curr] > height[nums[top - 1]]) {
int topIndex = nums[top - 1]; // 栈顶位置,最小值,有可能接到雨水的位置
top--; // 出栈
if (top == 0) break; // 栈为空,当前最小值之前没有别的了,没法接到雨水
int distance = curr - nums[top - 1] - 1; // 距离为当前高度减去当前栈顶位置减1
int boundedHeight = fmin(height[curr], height[nums[top - 1]]) - height[topIndex]; // 高度为最小高度左右两边的最小高度减去最小高度
ans += distance * boundedHeight;
}
nums[top++] = curr++; // 当前高度小于等于栈顶元素对应的高度,入栈
}
free(nums);
return ans;
}