42.接雨水
这道题我第一次做的时候我记得是维护单调队列的解法,现在再来看看吧,好的,维护单调队列,这边记录高度的同时,也要记录这一块高度的宽度是多少了,所以用了一个结构体,然后因为出队列是一个先进后出的形式,但是又要访问队首元素,综合考量用deque比较合适。
具体queue和deque的区别见 https://zhuanlan.zhihu.com/p/77981148
class Solution {
public:
struct node
{
int h,x;
node(int h,int x):h(h),x(x){}
};
int trap(vector& height) {
int n=height.size();
if (n==0) return 0;
deque dq;
node w(height[0],1);
dq.push_back(w);
int ans=0;
for (int i=1;i=dq.front().h)
{
int hnow=dq.front().h;
while (!dq.empty())
{
ans+=(hnow-dq.back().h)*dq.back().x;
dq.pop_back();
}
dq.push_back(node(height[i],1));
}
else
{
int hnow=height[i];
int xnow=1;
while (height[i]>=dq.back().h)
{
ans+=(hnow-dq.back().h)*dq.back().x;
xnow+=dq.back().x;
dq.pop_back();
}
dq.push_back(node(height[i],xnow));
}
}
return ans;
}
};
另外貌似还有双向记录的方法,我们再来康康,即记录前缀最大和后缀最大的方式,跟有一道记录前缀和还有记录后缀和的思路一样。即题目原意是对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值,那么
算法
找到数组中从下标 i 到最左端最高的条形块高度 left_max。
找到数组中从下标 i 到最右端最高的条形块高度 right_max。
扫描数组 height 并更新答案:
累加 min(max_left[i],max_right[i])−height[i] 到 ans 上
class Solution {
public:
int trap(vector& height)
{
if(height.size() == 0)
return 0;
int ans = 0;
int size = height.size();
vector left_max(size), right_max(size);
left_max[0] = height[0];
for (int i = 1; i < size; i++) {
left_max[i] = max(height[i], left_max[i - 1]);
}
right_max[size - 1] = height[size - 1];
for (int i = size - 2; i >= 0; i--) {
right_max[i] = max(height[i], right_max[i + 1]);
}
for (int i = 1; i < size - 1; i++) {
ans += min(left_max[i], right_max[i]) - height[i];
}
return ans;
}
};
但是这要开销O(2n)的空间复杂度,用deque只要O(n),但是如果面试的时候遇到这道题,建议采用这种双向记录的方法,比较好理解并且编码比较简单。
好的,我找到那道记录前缀乘积和后缀乘积的题了 238.除自身以外数组的乘积 这边稍微对比记录一下。
class Solution {
public:
vector productExceptSelf(vector& nums) {
int n=nums.size();
vector ans(n);
vector left_mul(n),right_mul(n);
left_mul[0]=1;
for (int i=1;i=0;--i)
right_mul[i]=nums[i+1]*right_mul[i+1];
for (int i=0;i
另外还有一种双指针的算法,非常巧妙。
定理一:在某个位置i
处,它能存的水,取决于它左右两边的最大值中较小的一个。
定理二:当我们从左往右处理到left下标时,左边的最大值left_max对它而言是可信的,但right_max对它而言是不可信的。
定理三:当我们从右往左处理到right下标时,右边的最大值right_max对它而言是可信的,但left_max对它而言是不可信的。
对于位置left
而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max,这时候,如果left_max
left_max
class Solution {
public:
int trap(vector& height)
{
int left = 0, right = height.size() - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left <= right) {
if (left_max < right_max) {
height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
++left;
}
else {
height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
--right;
}
}
return ans;
}
};
总结:其实我最开始的写法有点长,其实只要单调stack就能解决,不需要判断栈底的元素,只需要跟栈顶的元素一次比较,然后存下标就行了,因为存高度的话没办法记录下标宽度信息,但如果记录下标i,那么通过height[i]就可以得到高度信息,通过下标相减得到宽度信息。(注意这里相减的是top()-1的相减,即 栈中为ab当前为c,并且height[b] 407.接雨水2 从接雨水1可以看到必须是一个凹坑才能积水,维护的是一维上的,而这边则是要维护二维上的,原来维护两边,现在要维护一个圈,这是第一难点,第二个难点是很难想到怎么维护,这边是用优先队列找到这个圈的最短的那块板,逐步向内灌水,神奇的是原来一个圈,如果逐步向内,是不会破坏圈的性质的,相当于向四个方向灌水,会重新建立起木板,这道题用到的模板还是走迷宫的bfs的模板,而且从queue变成priority_queue而已,这样看来就是普通的优先队列+bfs了。 注意: 1.数组vis和hm都记得开大一点 2.q.push({i,j,hm[i][j]});是一种写法,q.push(node(i,j,hm[i][j]));应该也是一种写法。 然后因为我把原矩阵从[0..n-1][0..m-1]移到了[1..n][1..m]所以写得有些麻烦,如果不移可能写得更简单,见下。 题解中这个思路感觉说的比较清楚,可以参考: https://leetcode-cn.com/problems/trapping-rain-water-ii/solution/407-jie-yu-shui-zui-xiao-dui-cjie-fa-by-he-qian-3/ class Solution {
private:
int foot[4][2]={-1,0,1,0,0,-1,0,1};
struct node
{
int x,y,h;
node(int x,int y,int h):x(x),y(y),h(h) {}
friend bool operator < (node a,node b)
{
return a.h>b.h;
}
};
public:
int trapRainWater(vector
class Solution {
private:
int foot[4][2]={-1,0,1,0,0,-1,0,1};
struct node
{
int x,y,h;
node(int x,int y,int h):x(x),y(y),h(h) {}
friend bool operator < (node a,node b)
{
return a.h>b.h;
}
};
public:
int trapRainWater(vector