原题链接
碎碎念在前面
没想到第一次做leetcode的hard题就是在cpp机考的时候,我真的哭得好大声
机考的时候根据给的例子找了种方法出来,结果只符合那个栗子,其它用例都过不了×
然后就一直在纠结这个算法
直到最后的时候好像稍微有点思路了,但没来得及写很多
不过知道这题是leetcode上的,所以结束了就去用最后想到的思路写了一下,代码如下
我的解法
大致思路是:
维护两个指针一头一尾,分层计算收集到的雨水
中间需要注意的点比较多:
- 头尾的0要去掉
- 指针向中间移动时,要注意跳过那些高度比之前指过的最大值小的夹板(不然会重复计算)
- 根据1&2移动指针时要注意保持begin < end,不然会debug error(也就是数组越界)
- 需要记录当前已经进行的过程中最大的基准(就是代码中的max_smaller),以避免重复计算
糟糕情况下时间复杂度会达到O(n²)
int trap(vector<int>& height) {
int begin = 0;
int end = height.size() - 1;
int smaller_pre = 0;
int rain_num = 0;
int first = 1;
int max_smaller = 0;
while (begin < end) {
while ((height[begin] == 0 || height[begin] == smaller_pre) && begin < end) {
begin++;
}
while (height[end] == 0 || height[end] == smaller_pre && begin < end) {
end--;
}
int bigger = height[begin];
int smaller = height[end];
if (bigger < smaller) {
int tmp = bigger;
bigger = smaller;
smaller = tmp;
}
if (smaller >= max_smaller) {
for (int i = begin + 1; i < end; i++) {
if (height[i] < smaller) {
if (first) {
rain_num += (smaller - height[i]);
}
//else if (height[i] == 0) {
// rain_num += (smaller - smaller_pre - height[i]);
//}
else {
if (height[i] < max_smaller)
rain_num += (smaller - max_smaller);
else
rain_num += (smaller - height[i]);
}
}
}
}
first = 0;
if (height[begin] == smaller) {
begin++;
}
if (height[end] == smaller) {
end--;
}
smaller_pre = smaller;
if (smaller > max_smaller) {
max_smaller = smaller;
}
}
return rain_num;
}
其它解法
同样是维护头尾两个指针,时间复杂度最差情况下是O(n)
要点:
- 记录左右两边各自的最大height
- 计算的其实是这两个(当下)最大height中的height部分能够存多少雨水
- 可行原因:
3.1 两个夹板(也就是height)中间,除了那些比他们高的夹板之外,其它更小的夹板都会有空挡存储雨水
3.2 这些存储雨水的空挡的大小由这两个夹住它们的夹板的大小决定(应该是由更小的那个决定,这就是下面代码中,while循环内的最外层if-else体现的)
int trap(vector<int>& height) {
//***********************2 Pointers ***************************
// Time = O(N) and Space = O(1)
std::ios::sync_with_stdio(false);
if(height.size() == 0){
return 0;
}
int left = 0, right = height.size() - 1;
int ans = 0;
int leftMax = 0, rightMax = 0;
while(left < right){
if(height[left] < height[right]){
if(height[left] < leftMax){
ans += leftMax - height[left];
}
else{
leftMax = height[left];
}
left++;
}
else //height[left] >= height[right]
{
if(height[right] >= rightMax) {
rightMax = height[right];
}else{
ans += rightMax - height[right];
}
right--;
}
}
return ans;
}
总结与反思
- 关于机考时候的思路,一直被局限在max、now、right三个值的比较上,虽然模模糊糊感觉到了一个夹板能存多少雨水是需要看两侧的夹板的,但这个两侧太局限了,仅关注这三个值会带来很多的问题,而且最终也得不出答案
- 机考快要结束的时候想到的那个思路,也就是上面我自己的解法,是可行的,但中间有许多需要注意的地方,基本上是为了避免重复
- 其它解法中(也就是leetcode上cpp解法下最快的一种解法),应该说这种方法是抓住了这个问题的精髓,能够存储多少雨水其实是由这个夹板两侧的最高的两个夹板中相对更小的那个夹板决定的,所以从头尾向中间走才应该是真正的解决办法