leetcode - [DP、棧、雙指針] - 接雨水(42)

1、問題描述

給定n個非負整數,每個整數表示一個寬度爲1的柱子的高度,
將這n個柱子並排放在一起,問按這種方式排列得柱子在下雨時能接多少雨水。
在這裏插入圖片描述
上面是由數組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。
示例:

輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6

2、解題思路

  • 解決這道問題有以下幾種方法:
  • 方法1:暴力法。我們可以遍歷每個柱子ii,然後在ii的左邊找到最高的柱子leftmax[i]leftmax[i],再在ii的右邊找到最高的柱子rightmax[i]rightmax[i],這樣,對於柱子ii而言,它能夠盛水的容量就等於leftmax[i]leftmax[i]rightmax[i]rightmax[i]兩者之中的最小值減去柱子ii的高度,即minleftmax[i],rightmax[i]height[i]min{leftmax[i],rightmax[i]} - height[i].這種方法的時間複雜度爲O(n2)O(n^2),空間複雜度爲O(1)O(1)
    算法如下所示:
輸入:高度數組height;
輸出:盛水容量ans;
算法步驟:
初始化ans = 0;
i從左向右掃描數組:
	初始化leftmax = 0, rightmax = 0;
	j從當前元素(i所指向的元素)向左掃描並更新leftmax:
		leftmax = max(leftmax,height[j]);
	k從當前元素(i所指向的元素)向右掃描並更新rightmax:
		rightmax = max(rightmax,height[k]);
	將min{leftmax, rightmax} - height[i]累加到ans;

*** 方法2:動態規劃。**上述暴力法每次在求解leftmax[i]leftmax[i]rightmax[i]rightmax[i]時,都需要向左和向右掃描一遍,其實這不是必須的,我們可以提前計算出每個柱子iileftmax[i]leftmax[i]rightmax[i]rightmax[i],這樣在遍歷的過程中就避免了向左和向右的掃描過程。我們使用長度爲lenlen的兩個數組leftmaxleftmaxrightmaxrightmax來保存每個柱子iileftmax[i]leftmax[i]rightmax[i]rightmax[i]。由於遍歷一次,故這種方法的時間複雜度爲O(n)O(n),使用兩個額外數組,故空間複雜度爲O(n)O(n)

  • 方法3:棧。我們在遍歷數組的過程,動態維持一個棧,如果當前元素ii 小於等於棧頂元素s.tops.top,則說明棧頂元素從左邊限定了當前元素,直接把當前元素的索引壓入到棧中;如果當前元素大於棧頂元素,則說明當前元素從右邊限定了棧頂元素,而由於棧頂元素在棧中的前一個元素剛好是棧頂元素的左邊界,此時應該彈出棧頂元素ee,並將distance×bounddistance \times bound累加到ansans,其中,
    distance=min{height[s.top]height[i]}height[e]distance =min\{height[s.top],height[i] \} - height[e]
    bound=is.top1bound= i - s.top - 1
    單次遍歷,每個元素最多訪問兩次(由於出棧和入棧),故時間複雜度爲O(n)O(n)。空間複雜度爲O(n)O(n),棧最多在階梯型或平坦型條形塊結構中佔用 O(n)O(n)的空間。
算法過程:
* 使用棧s用來存儲柱子的索引,s初始爲空;
*初始化ans = 0;
*i從左到右掃描數組:
	#當棧s非空並且height[i] > height[s.top]:
		*彈出棧頂元素e;
		*如果此時棧不爲空:
			#計算當前元素與棧頂元素的距離,準備填充:distance = i - s.top - 1;
			#計算界定高度,bound = min(height[i],height[s.top]) - height[e];
			#將distance * bound 累加到ans中;
	#棧爲空或者height[i] <= height[s.top]時,將i壓入棧中。
  • 方法4:雙指針。根據方法2我們可以發現,柱子i可存儲的積水高度將由leftmax[i]和rightmax[i]中的最小值決定。
    所以我們可以認爲如果一端有更高的柱子(比如右端),則積水的高度由另一端的方向決定(從左到右)。當我們發現右端的柱子不再是最高的,則從相反的方向開始遍歷(從右向左)。我們必須在遍歷時維護leftmax和rightmax,但是我們現在可以使用兩個指針交替進行,所以只需遍歷一次即可完成。
算法過程:
#初始化ans = 0;
#初始化i=0,j=size-1;
#初始化leftmax = height[0],rightmax = height[size-1];
#當i < j時:
	*如果height[i] < height[j]:
		#如果height[i] >= leftmax, 更新leftmax=height[i];
		#否則累加到ans,ans+=leftmax - height[i];
		#i++;
	*否則:
		#如果height[j]>=leftmax,更新rightmax = height[j];
		#否則累加到ans, ans+=rightmax - height[j];
		#j--;

舉個例子:
假設height=[6,4,3,7,2,4,5]
leftmax = -1,rightmax = -1;
(1)i=0,j=6,height[i] > height[j],height[j] > rightmax,故rightmax = height[j] = 5,j = 5;
(2)i=0,j=5,height[i] > height[j],height[j] < rightmax,故ans+=rightmax - height[j] = 1,j = 4;
(3)i=0,j=4,height[i] > height[j],height[j] <rightmax,故ans+=rightmax - height[j] = 1 + 3 ,j = 3;
(4)i=0,j=3,height[i] < height[j],height[i] > leftmax,故
leftmax = height[i] = 6,i = 1;
(5)i=1,j=3,height[i]<height[j],height[i] < leftmax,故ans+=leftmax - height[i] = 2,i = 2;
(6)i=2,j=3,height[i]<height[j],height[i] < leftmax,故ans+=leftmax - height[i] = 2 + 3,i = 3;
(7)i ==j

3、代碼實現

class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0;
        int right = height.size() - 1;
        int leftmax = -1;
        int rightmax = -1;
        int ans = 0;
        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;

        
    }
};

問題擴展:如果變成三維平面,該怎麼做?
接雨水II

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章