LC42——接雨水(单调栈、动态规划)

接雨水问题

原题目:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
LC42——接雨水(单调栈、动态规划)_第1张图片

1、 简单解法

首先,直观上来看能接雨水的柱子的两边肯定有比它更高的柱子。那么如何确定一根柱子可以接多少雨水呢?如前一句分析,与它左边最高的柱子和右边最高的柱子有关,两边最高的柱子中的最低的柱子减去该柱子就是这根柱子可以接到的雨水量了。因此我们可以循环遍历每一根柱子,找到其左边最高的柱子和右边最高的柱子,然后就可以计算接到的雨水量了。时间复杂度为 o ( n 2 ) o(n^2) o(n2)

class Solution {
public:
	int trap(vector<int>& height) {
		int left = 0, right = 0;
		int len = height.size();
		int sum = 0;
		//遍历第二个到倒数第二个
		for (int i = 1; i < len - 1; i++)
		{
			left =i-1;
			right = i + 1;
			int leftMax = INT_MIN;
			int rightMax = INT_MIN;
			//遍历该点的左边
			while (left >= 0)
			{
				leftMax = max(height[left], leftMax);
				left--;
			}
			//遍历该点的右边
			while (right <= len - 1)
			{
				rightMax = max(height[right], rightMax);
				right++;
			}
			//当左边的最大值与右边的最大值都比height[i]大时
			if (leftMax > height[i] && rightMax > height[i])
			{
				sum += min(leftMax, rightMax) - height[i];
			}
		}
		return sum;
	}
};

2、动态规划进行优化

在方法一中,我们在每一次遍历时都要重新计算一下柱子的左右两边的最大值,我们能否事先计算一下每个柱子两边的最大值存储在数组当中,然后在遍历的时候直接从数组中取出两边的最大值。时间复杂度为 o ( n ) o(n) o(n)

class Solution {
public:
	int trap(vector<int>& height) {
		int len = height.size();
		vector<int>leftMax(len);
		vector<int>rightMax(len);
		leftMax[0] = height[0];
		rightMax[len - 1] = height[len - 1];
		int sum = 0;
		//计算左边的最大值
		for (int i = 1; i < len - 1; i++)
			leftMax[i]=max(leftMax[i - 1], height[i]);	
		//计算右边的最大值
		for (int i = len-2; i >=0; i--)
			rightMax[i] = max(rightMax[i + 1], height[i]);
		//计算雨水量
		for (int i = 1; i < len - 1; i++)
			sum += min(leftMax[i], rightMax[i]) - height[i];
		return sum;
	}
};

3、双指针优化动态规划

在动态规划方法中,我们维护了两个数组分别来存储柱子两边的最大值。然而在遍历元素的过程中,计算某个柱子的雨水量时只用到了右边最大值和左边最大值的一个值,因此我们不像前两个方法一样循环遍历每根柱子来计算雨水量,而是在遍历过程中更新两边最大值中较小的那一个。

class Solution {
public:
	int trap(vector<int>& height) {
		int n = height.size();
		int left = 0, right = n - 1;
		int leftMax = 0, rightMax = 0;
		int sum = 0;
		while (left < right)
		{
			//当height[left] <= height[right]时,leftMax<=rightMax,所以计算雨水量时使用leftMax
			while (left < right&&height[left] <= height[right])
			{
				leftMax = max(leftMax,height[left]);
				left++;
				sum += (leftMax - height[left]>0? (leftMax - height[left]):0);
			}
			while (left < right&&height[right] <= height[left])
			{
				rightMax = max(rightMax,height[right]);
				right--;
				sum += (rightMax - height[right]>0?(rightMax - height[right]):0);
			}
		}
		return sum;
	}
};

4、单调栈

除了上述计算两边最大值的方法外,也可以使用单调栈的方法来解决。
维护一个单调递减的栈,栈中存放的柱子的下标,当新加入的元素比栈顶下标对应的元素大时,取出栈顶元素并弹出,计算弹出的下标对应的元素的雨水量。因为单调递减栈,所以再取出现在的栈顶元素为left,计算之前弹出的元素的雨水量。当新加入的元素小于或等于栈顶下标对应的元素时,直接弹出栈顶元素。整个过程可以理解为一层层雨水往上加。

class Solution{
public:
    int trap(vector<int>&height)
    {
        stack<int>stack;
        int sum=0;
        stack.push(0);

        for(int i=1;i<height.size();i++)
        {
            //新加入的元素比栈顶的对应的元素大
            while(!stack.empty()&&height[i]>height[stack.top()])
            {
                int top=stack.top();
                stack.pop();
                //如果栈中有两个及以上元素
                if(!stack.empty())
                {
                     int left=stack.top();
                     int width=i-left-1;
                     sum+=(min(height[i],height[left])-height[top])*width;
                }
              	 else
                  	 break;
                }
            		stack.push(i);
        }
        return sum;
    }
};

你可能感兴趣的:(算法,动态规划,算法,leetcode,数据结构)