接雨水这题可谓十分经典了。最近准备多做一些这种直方图相关的题目。
单调栈
首先看一下单调栈解法。
int trap(vector<int>& height) { stack<int> st; int ans = 0; int i = 0; while(i < height.size()){ while(!st.empty() && height[i] > height[st.top()]){ int oritop = st.top(); st.pop(); if(st.empty()) break; int newtop = st.top(); int fillHeight = min(height[i], height[newtop]) - height[oritop]; int distance = i - newtop - 1; ans += distance * fillHeight; } st.push(i); i += 1; } return ans; }
为什么说这是一个单调栈呢?因为在访问某个柱子之前,前面的柱子一定已经把小于它的那些柱子给 pop 掉了,只留下一个单调递减的栈。
while(!st.empty() && height[i] > height[st.top()])
所以当我们访问黄色柱子的时候,前面出现过的非单调柱子排列,已经在处理中被灌满水了。
现在我们又遇到一个递增的情况,此时我们需要做的是,找出栈顶柱子(红色)和栈顶之下的柱子(绿色)。
由于栈是单调递减的,因此绿色柱子的高度一定大于等于红色柱子。
所以黄色柱子和绿色柱子之间可以灌水(深色部分),灌到红色柱子的高度以上。
当然,绿色柱子不一定大于黄色柱子的高度。这时我们就可以再次循环,填补到更高,直到栈满足了单调递减为止。
注意,如果栈是空的话,说明没有承接的对手柱子了,那么也就不能灌水。(如图所示,框内的虚空水是无法灌入的。)
双指针
双指针法初看之下可能有点奇怪。啊,这也行?
首先我们介绍一个朴素的方法。
我们从左往右,每个柱子存储的虚空水高度记为其左边最高柱子。用蓝色记录。
再从右往左,每个柱子存储的虚空水高度记为其右边最高柱子。用黄色记录。
那么,此时着色为绿色的部分,就是实际存储的水。
编程中我们可以记录黄色虚空水和蓝色虚空水的高度,取其小值。
观察这个方法,我们发现,虚空水为什么不能变成实水?是因为我们并不知道另一边是否有对手柱子承接虚空水。
双指针法的原理就是,我们可以知道另一边至少存在一个柱子,使得我们的虚空水一定会被接住,变成实际的水。
例如,我们的右边指针指向蓝色柱子,左边指针指向黄色柱子。在这两根柱子之间有一根更高的柱子。
当前,我们并不知道灰色柱子的存在。但是,至少我们知道,如果我们以蓝色柱子的高度从左向右填水,那么总会遇到一个柱子可以围挡起来。
当然,当我们移动到了灰色柱子的位置,移动的指针就变成了右边。
总之,以此类推。
int trap(vector<int>& height) { if(height.size() == 0) return 0; int ans = 0; int left = 0, right = height.size() - 1; int leftmax = height[left], rightmax = height[right]; while(left < right){ if(height[left] < height[right]){ if(height[left] >= leftmax){ leftmax = height[left]; } else{ ans += leftmax - height[left]; } left++; } else{ if(height[right] >= rightmax){ rightmax = height[right]; } else{ ans += rightmax - height[right]; } right--; } } return ans; }