研究生畢業前一日三題:第一題,立方體上面存留雨水問題(Water Problem)
題目:給定一個數組,每個位置的數值代表一個高度。那麼整個陣列可以看作為一個直方圖。如果把這個直方圖當做容器的話,求這個容器能裝多少水?
例如:3,1,2,4
代表第一個位置高度為3,第二個位置高度為1,第三個位置高度為2,第四個位置高度為4.
3,1,2,4這個陣列代表的容器可以裝3格子的水!
(整體思路)最直接了當的做法是:求解出每一個立方體上面可以存留的水的體積,這些體積的累加和就是整個容器可以容納的水的體積。
方法一解析(暴力解法,時間複雜度N^2,空間複雜度1):(對每一個立方體,求解出這個立方體相對於整個陣列,左邊的最大值和右邊的最大值,然後,就可以確定出本立方體可以容納的水的多少,時間複雜度為:N個N相加,為N^2)
for(int i=0;i<arr.size();i++) { int maxLeft = INT_MIN; int maxRight = INT_MIN; for(int j=i;j>=0;j--) { maxLeft=max(maxLeft,arr[j]); } for(int k=i;k<arr.size();k++) { maxRight=max(maxRight,arr[k]); } int height = min(maxLeft,maxRight)-arr[i]; res += max(0,height); }
方法二解析(預處理陣列的方法,時間複雜度N,空間複雜度N):(從左往右生成一個當前值左邊最大值的陣列(最大值陣列),同理從右往左生成一個最大值預處理陣列,以後在遍歷陣列的時候,就可以直接取出當前值的左邊和右邊最大值,利用最根本的思路,求解出當前立方體可以容納的水的多少)
vector<int > leftMax; //不同push_back(),,用下標的時候得先分配大小; leftMax.resize(arr.size()); vector<int > rightMax; rightMax.resize(arr.size()); int res = 0; leftMax[0]=arr[0]; for(int i=1;i<arr.size();i++) { /*if(arr[i] >= leftMax[i-1]) { leftMax[i]=arr[i]; } else { leftMax[i]=leftMax[i-1]; }*/ //上面的if-else 可以用一句話結束 leftMax[i] = max( arr[i],leftMax[i-1] ); } rightMax[arr.size()-1]=arr[arr.size()-1]; for(int i=arr.size()-2; i>=0; i--) { if( arr[i] >= rightMax[i+1] ) { rightMax[i]= arr[i]; } else { rightMax[i]= rightMax[i+1]; } } for(int i=1;i<arr.size()-1;i++) { int height = min(rightMax[i],leftMax[i])-arr[i]; res += max(height,0); }
方法三解析(雙指標方法,時間複雜度N,空間複雜度1):(左右倆個指標分別為陣列的倆的端點,並且用兩個變數記錄左端的最大值和右端的最大值,兩個最大值中比較小的那個決定了容器的容積,最大值較小的那一側立方體的容積可以求出,然後移動這一側的指標,直到左右指標碰頭)
int res=0;
int leftMax=arr[0];
int rightMax = arr[arr.size()-1];
int left=1;
int right = arr.size()-2;
while(left<=right)
{
if(leftMax <= rightMax )
{
res +=max( leftMax-arr[left],0);
leftMax = max(arr[left++],leftMax);
}
else
{
res +=max( rightMax-arr[right],0);
rightMax = max(arr[right--],rightMax);
}
}
到此,三種方法都講解完畢。體現了演算法暴力求解到抓住問題的本質,從時間和空間複雜度上優化到極致的過程,更快,更小空間求解。
擴充套件題目:2018年騰訊校招題目(領釦11,盛最多水的容器)
給定 n 個非負整數 a1,a2,...,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少為 2。
圖中垂直線代表輸入陣列 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示為藍色部分)的最大值為 49。
示例:
輸入: [1,8,6,2,5,4,8,3,7] 輸出: 49
思路:
方法一暴力求解方法:每一個杆子往右擴,求解出每一種情況的容積,找出最大值。
int areaMax=0;
int areaCur=0;
for(int i=0;i<arr.size();i++)//暴力解法:時間複雜度N^2,空間複雜度1
{
for(int j=i+1;j<arr.size();j++)
{
areaCur = min(arr[i],arr[j])*(j-i);
areaMax =max(areaMax,areaCur);
}
}
方法二指標方法:根據面積大小為min(srr[left],arr[right])*(right-left),哪邊的板子低,這個低的板子決定了容積的大小,所以低的這一側移動指標,在移動指標的時候,記錄容積當前的最大值。
int left=0;
int right=arr.size()-1;
int areaCur=0;
int areaMax=0;
while(left<right)
{
areaCur = min(arr[left],arr[right])*(right-left);
areaMax = max(areaMax,areaCur);
if(arr[left]<arr[right])
{
left++;
}
else
{
right--;
}
}
兩道題目整理完畢,入手點是暴力求解找出問題的解法,然後抓住問題的本質,簡化解題方法。