1. 程式人生 > >研究生畢業前一日三題:第一題,立方體上面存留雨水問題(Water Problem)

研究生畢業前一日三題:第一題,立方體上面存留雨水問題(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,每個數代表座標中的一個點 (iai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (iai) 和 (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--;		
		}
	}

兩道題目整理完畢,入手點是暴力求解找出問題的解法,然後抓住問題的本質,簡化解題方法。