接青豆的几种姿势

63 阅读1分钟

当青训营遇上码上掘金

这里是接青豆一题的题解。

初次看到就觉得单调栈可以一试。单调栈求的是某个点之前第一个比它大/比它小的点。之前我总是喜欢往栈里存值,后来做得多了发现存值大概率浪费了这个栈,因为值是可以通过输入的数组O(1)求出的,所以我们往里面扔下标:

class Solution { 
    public: int trap(vector<int>& height) {
        //这是一个单调递减栈
        stack<int> st; 
        int n = height.size(),ans = 0; 
        for(int i = 0;i<n;i++) { 
            //last是上一个最小的柱子,刚被弹出,将要成为坑底
            int last = -1; 
            //找到第一个大于等于i的,不满足的出栈
            //这个出栈的过程中,被出栈的都可以和前面在栈中的元素+最新的这个柱子围成一个他们自己的坑来接青豆
            //这个坑的大小就是下面这个公式(只计算和last的高度差的那部分是因为更低洼处已经在前面计算过了
            while(!st.empty() && height[st.top()]<height[i]) {
                if(last!=-1) ans+=(min(height[st.top()],height[i])-height[last])*(i-st.top()-1); 
                last = st.top(); 
                st.pop(); 
            }
            //终于找到了一个更高的,那这个更高的还能和新来的整一个坑
            if(!st.empty() && last!=-1) { 
                ans+=(min(height[st.top()],height[i])-height[last])*(i-st.top()-1); 
            } 
            //新来的入栈
            st.push(i); 
        } 
        return ans; 
    } 
};

时间复杂度O(n)

空间复杂度O(n)

进阶:能不能用常数空间解决?

能!

我们这样想:某个点处能不能接青豆全看两边能不能兜住,对于每一个列来说,只要左边有比它更高的,右边也有比它更高的,那它起码就能兜住1*(左边高度最大值和右边高度最大值中更小的那个 - 它的高度),因为只看它自己嘛。

既然下标 i 处能接的青豆量由leftMax[i] 和 rightMax[i] 中的最小值决定。由于数组 leftMax 是从左往右计算,数组 rightMax 是从右往左计算,因此可以使用双指针和两个变量代替两个数组。

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int l = 0,r = n-1,leftMax = height[0],rightMax = height[n-1],ans = 0;
        while(l<=r) {
            //左边最大值更小就走左边,否则右边
            if(leftMax <= rightMax) {
                ans += leftMax - height[l++];
                if(l>=n) break;
                //加完青豆,更新l和max
                leftMax = max(height[l],leftMax);
            }else {
                ans += rightMax - height[r--];
                if(r<0) break;
                rightMax = max(height[r],rightMax);
            }
        }
        return ans;
    }
};