1. 程式人生 > >LeetCode11. Container With Most Water(思維題:選擇左右邊使得容器所盛水最多)

LeetCode11. Container With Most Water(思維題:選擇左右邊使得容器所盛水最多)

這道題,我獨立想了好久!!!!一道很好的思維題!!!!!!!!

思路1是自己的求解方法O(nlogn),思路2是網上最優的解法O(n),暴利O(n^2)肯定超時!

Given n non-negative integers a1a2, ..., an, where each represents a point at coordinate (iai). n vertical lines are drawn such that the two endpoints of line i is at (iai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant(傾斜) the container.

解題思路1:性質O(nlogn)

從結論思考有這樣一條性質不難發現:如果l,r是最優的邊,那麼l左邊一定不存在比它大的,同理,r右邊也不存在比它大的;

所以,我們可以預處理出所有可能是l,r的邊,條件是比它左(右)邊的最大值要大。最終得到的肯定是如下形狀的陣列(中心是全域性的最大值):


接下來,需要考慮的是怎樣找到全域性的最優值,我的做法是(預設左低右高)從左半邊開始遍歷,同時二分右邊找到大於等於左邊元素的值,兩條邊構成容器,計算面積更新ans;然後反過來,(預設左高右低),從右半邊開始遍歷,同時二分左邊找到大於等於右邊元素的值,這兩邊也構成容器,計算面積再更新ans;複雜度O(nlogn)!

程式碼中注意二分bsearch()返回的是陣列中大於等於所找元素v的值在初始height[]容器中的下標!

參考程式碼1:

#define eps 1e-8

struct node{
  int h,p;  
  node(int H,int P):h(H),p(P){}
};

vector<struct node>v1;
vector<struct node>v2;

int bsearch1(int v){
    int l = 0,r = v2.size(),m;
    while(l < r){
        m = (l+r) >> 1;
        if(v2[m].h == v) return v2[m].p;
        if(v2[m].h < v)  l = m+1;
        else r = m ;
    }
    return v2[r].p;
}

int bsearch2(int v){
    int l = 0,r = v1.size(),m;
    while(l < r){
        m = (l+r) >> 1;
        if(v1[m].h == v) return v1[m].p;
        if(v1[m].h < v)  l = m+1;
        else r = m;
    }
    return v1[r].p;
}

class Solution {
public:
    int maxArea(vector<int>& height) {
      int n = height.size();
       int dp[2][n];//dp[0][]從左到右最大值,dp[1][]從右到左最大值
       dp[0][0] = height[0];
       for(int i = 1;i < n;++ i){
           dp[0][i] = max(height[i],dp[0][i-1]);
       }

       v1.clear();
       v1.push_back(node(height[0],0));
       for(int i = 1;i < n;++ i){
           if(height[i] > dp[0][i-1]) v1.push_back(node(height[i],i));
       }
       dp[1][n-1] = height[n-1];
       for(int i = n-2;i >=0; -- i){
           dp[1][i] = max(height[i],dp[1][i+1]);
       }

       v2.clear();
       v2.push_back(node(height[n-1],n-1));
       for(int i = n-2;i >= 0;-- i){
           if(height[i] > dp[1][i+1]) v2.push_back(node(height[i],i));
       }
       int ans = 0;
       for(int i = 0;i < v1.size();++ i){//左低右高
           int area = v1[i].h*(bsearch1(v1[i].h) - v1[i].p);
           ans = max(ans,area);
       }
       for(int i = 0;i < v2.size();++ i){//左高右低
           int area = v2[i].h*(v2[i].p - bsearch2(v2[i].h));
           ans = max(ans,area);
       }
       return ans;
    }
};


解題思路2單調性O(n)

這個我感覺是一種單調性,我開始的思路是l不動,r增加,發現面積的變化是不確定的;所以換一種思路,讓r減小,你會發現:當height[l] < height[r]時,height[l] 與 height[l+1 ... r-1]構成的容器體積都比height[r]小,這就是一種單調性,可以作O(n^2) -> O(n)的多餘情況的剔除!

初始時預設l = 0,r = height.size() - 1,讀者可以在草稿紙上作各種情景的模擬,你會發現有這樣的比較過程:

首先,純暴力,我們要比較C(n,2) = n * (n - 1) / 2種情況;

然後,初始時比較height[l = 0] 與 height[r = n - 1],不管height[l]、height[r]孰大孰小,我們總會剔除n - 2種組合,是不是?

不妨假設height[l] < height[r].接著,除了(height[l],height[r])這種組合,height[l]已經不適合作左邊,那麼看下一種情況l++;同樣地,比較當前的height[l] 、 height[r],我們又會剔除 n - 3種組合;

依次類推,直到最後,兩條臨近的邊,不用剔除;

第一次:比較O(1)  + 剔除O( n - 2 )

第二次:比較O(1)  + 剔除O( n - 3 )

第三次:比較O(1)  + 剔除O( n - 4 )

....

第n-2次:比較O(1)  + 剔除O( 1 )

第n-1次:比較O(1)  + 不剔除

每次都會計算一個組合,並剔除多種組合,所以,總共比較了(n-1) + sigma(n-2,n-3,0) = 0+1+....+n-1 = n*(n-1)/2 = C(n,2)種組合,這正好與純暴力比較的組合數目一致。

因為利用單調性,每次比較都剔除了多種組合,總的複雜度降低了!

參考程式碼2:

class Solution {
public:
    int maxArea(vector<int>& height) {
       int n = height.size();
       int l = 0, r = n - 1;
       int ans = 0;
       while(l < r){
           ans = max(ans,min(height[l],height[r])*(r-l));
           if(height[l] < height[r]){
               ++ l;
           }
           else -- r ;
       }
       return ans;
    }
};


P.S.自己思考總結的東西,記憶是最深刻\收穫是最多的,光看別人的題解,沒有多少演算法能力的提高~