LeetCode 11 - Container With Most Water


LeetCode 407 - Trapping Rain Water II
[LeetCode]Container With Most Water, 解题报告 - 小一的专栏 - 博客频道 - CSDN.NET
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) 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.


https://leetcode.com/articles/container-most-water/
We have to maximize the Area that can be formed between the vertical lines using the shorter line as length and the distance between the lines as the width of the rectangle forming the area.
The intuition behind this approach is that the area formed between the lines will always be limited by the height of the shorter line. Further, the farther the lines, the more will be the area obtained.
We take two pointers, one at the beginning and one at the end of the array constituting the length of the lines. Futher, we maintain a variable maxarea to store the maximum area obtained till now. At every step, we find out the area formed between them, update maxarea and move the pointer pointing to the shorter line towards the other end by one step.
Initially we consider the area constituting the exterior most lines. Now, to maximize the area, we need to consider the area between the lines of larger lengths. If we try to move the pointer at the longer line inwards, we won't gain any increase in area, since it is limited by the shorter line. But moving the shorter line's pointer could turn out to be beneficial, as per the same argument, despite the reduction in the width. This is done since a relatively longer line obtained by moving the shorter line's pointer might overcome the reduction in area caused by the width reduction.
Proved by contradiction:
Suppose the returned result is not the optimal solution. Then there must exist an optimal solution, say a container with a_ol and a_or (left and right respectively), such that it has a greater volume than the one we got. Since our algorithm stops only if the two pointers meet. So, we must have visited one of them but not the other. WLOG, let's say we visited a_ol but not a_or. When a pointer stops at a_ol, it won't move until
  • The other pointer also points to a_ol.
    In this case, iteration ends. But the other pointer must have visited a_or on its way from right end to a_ol. Contradiction to our assumption that we didn't visit a_or.
  • The other pointer arrives at a value, say a_rr, that is greater than a_ol before it reaches a_or.
    In this case, we does move a_ol. But notice that the volume of a_ol and a_rr is already greater than a_ol and a_or (as it is wider and heigher), which means that a_ol and a_or is not the optimal solution -- Contradiction!
Both cases arrive at a contradiction.

Here is a simple proof for the solution.
Use v[low, high] indicates the volume of container with low and high. suppose height[low] < height[high], then we move low to low+1, that means we ingored v[low, high-1],v[low, high-2],etc, if this is safe, then the algorithm is right, and it's obvious that v[low, high-1],high[low, high-2]...... can't be larger than v[low, high] since its width can't be larger than high-low, and its height is limited by height[low].
https://discuss.leetcode.com/topic/3462/yet-another-way-to-see-what-happens-in-the-o-n-algorithm
https://discuss.leetcode.com/topic/25004/easy-concise-java-o-n-solution-with-proof-and-explanation/
AKA, the general idea to find some max is to go through all cases where max value can possibly occur and keep updating the max value. The efficiency of the scan depends on the size of cases you plan to scan.
To increase efficiency, all we need to do is to find a smart way of scan to cut off the useless cases and meanwhile 100% guarantee the max value can be reached through the rest of cases.
In this problem, the smart scan way is to set two pointers initialized at both ends of the array. Every time move the smaller value pointer to inner array. Then after the two pointers meet, all possible max cases have been scanned and the max situation is 100% reached somewhere in the scan. Following is a brief prove of this.
Given a1,a2,a3.....an as input array. Lets assume a10 and a20 are the max area situation. We need to prove that a10 can be reached by left pointer and during the time left pointer stays at a10, a20 can be reached by right pointer. That is to say, the core problem is to prove: when left pointer is at a10 and right pointer is at a21, the next move must be right pointer to a20.
Since we are always moving the pointer with the smaller value, i.e. if a10 > a21, we should move pointer at a21 to a20, as we hope. Why a10 >a21? Because if a21>a10, then area of a10 and a20 must be less than area of a10 and a21. Because the area of a10 and a21 is at least height[a10] * (21-10) while the area of a10 and a20 is at most height[a10] * (20-10). So there is a contradiction of assumption a10 and a20 has the max area. So, a10 must be greater than a21, then next move a21 has to be move to a20. The max cases must be reached.
public int maxArea(int[] height) {
    int left = 0, right = height.length - 1;
 int maxArea = 0;

 while (left < right) {
  maxArea = Math.max(maxArea, Math.min(height[left], height[right])
    * (right - left));
  if (height[left] < height[right])
   left++;
  else
   right--;
 }

 return maxArea;
}
https://rafal.io/posts/leetcode-11-container-with-most-water.html
The naive version would solve this in O(n2) time. It can be improved as follows to linear time:
Keep two indices i and j, that start on both ends of the array to process, height. Our final goal is to findi,j, such that
A(i,j)=(ji)×min(height(i),height(j))
is maximized. At every step, if height[i] < height[j], move i up by one. Otherwise move j down by one, until the two pointers meet. We now show that this gives the maximal answer. Suppose we just computed A(i,j), and height[i] < height[j]. If we increase i by one, we will ignore all the values A(i,k) for ik<j. We now show that A(i,j)>A(i,k). Since height[i] < height[j]A(i,k) is bounded by the height[i] term, and since k<j, it follows that A(i,j)>A(i,k), and we can safely ignore all of the A(i,k) and move on to inspecting A(i+1,j). The case with height[i] > height[j] is symmetric.



public int maxArea(int[] height) {
  int maxA = 0;
  int i = 0;
  int j = height.length-1;

  while(i < j){
    int curArea = (j-i) * (Math.min(height[i], height[j]));
    maxA = Math.max(curArea,maxA);
    if(height[i] < height[j]) i++;
    else j--;
  }

  return maxA;
}
Initially we can assume the result is 0. Then we scan from both sides. If leftHeight < rightHeight, move right and find a value that is greater than leftHeight. Similarily, if leftHeight > rightHeight, move left and find a value that is greater than rightHeight. Additionally, keep tracking the max value.
public int maxArea(int[] height) {
 if (height == null || height.length < 2) {
  return 0;
 }
 
 int max = 0;
 int left = 0;
 int right = height.length - 1;
 
 while (left < right) {
  max = Math.max(max, (right - left) * Math.min(height[left], height[right]));
  if (height[left] < height[right])
   left++;
  else
   right--;
 }
 
 return max;
}
  1. The widest container (using first and last line) is a good candidate, because of its width. Its water level is the height of the smaller one of first and last line.
  2. All other containers are less wide and thus would need a higher water level in order to hold more water.
  3. The smaller one of first and last line doesn't support a higher water level and can thus be safely removed from further consideration.
    def maxArea(self, height):
        i, j = 0, len(height) - 1
        water = 0
        while i < j:
            water = max(water, (j - i) * min(height[i], height[j]))
            if height[i] < height[j]:
                i += 1
            else:
                j -= 1
        return water
Further explanation:
Variables i and j define the container under consideration. We initialize them to first and last line, meaning the widest container. Variable water will keep track of the highest amount of water we managed so far. We compute j - i, the width of the current container, and min(height[i], height[j]), the water level that this container can support. Multiply them to get how much water this container can hold, and update water accordingly. Next remove the smaller one of the two lines from consideration, as justified above in "Idea / Proof". Continue until there is nothing left to consider, then return the result.
当从两边向中间考虑时,乘水的面积是由(两端较小的高度)×(两个板之间的宽度)决定的。
假定初始的盛水面积为ans=0,lh为左边的高度,rh为右边的高度,如果lh < rh, 则向右运动,寻找第一个比当前lh大的左节点。同理,如果lh > rh,则向左运动,寻找第一个比当前rh大的右节点。
截止条件为坐标L >= R。
  1.     public int maxArea(int[] height) {  
  2.         int i, j, lh, rh, area, tmp, len = height.length;  
  3.   
  4.         lh = height[0];  
  5.         rh = height[len - 1];  
  6.         area = 0;  
  7.         i = 0;  
  8.         j = len - 1;  
  9.           
  10.         while (i < j) {  
  11.             tmp = Math.min(lh, rh) * (j - i);  
  12.               
  13.             if (tmp > area) {  
  14.                 area = tmp;  
  15.             }  
  16.               
  17.             if (lh < rh) {  
  18.                 while (i < j && height[i] <= lh) {  
  19.                     i ++;  
  20.                 }  
  21.                 if (i < j) {  
  22.                     lh = height[i];  
  23.                 }  
  24.             } else {  
  25.                 while (i < j && height[j] <= rh) {  
  26.                     j --;  
  27.                 }  
  28.                 if (i < j) {  
  29.                     rh = height[j];  
  30.                 }  
  31.             }  
  32.         }  
  33.   
  34.         return area;  
  35.     }  
http://www.cnblogs.com/TenosDoIt/p/3812880.html
http://www.voidcn.com/blog/luke2834/article/p-6071589.html
  • 我们先把柱子从低到高排序,然后在遍历这个有序集合中的每个柱子时,我们维护两个变量l,r记录比该柱子高的最左和最右位置。
  • 具体的方法,是用一个mark数组已经遍历过哪些位置的柱子,每遍历第i个柱子后,令mark[i]=1,然后让l和r分别向右、向左移动到第一个mark不是1的位置
  • 这样移动总共只有n次吗,总的时间复杂度是排序的O(nlogn)
  •     vector<pair<int, int> > vec;
        int maxArea(vector<int>& height) {
            for(int i = 0; i<height.size(); i++){
                vec.push_back({height[i], i});
            }
            vector<int> mark(vec.size(), 0);
            sort(vec.begin(), vec.end());
            int l = 0, r = height.size() - 1;
            int ans = 0;
            for (int i=0;i<vec.size();i++){
                ans = max(ans, vec[i].first * (r - vec[i].second));
                ans = max(ans, vec[i].first * (vec[i].second - l));
                mark[vec[i].second] = 1;
                while (mark[l] == 1)
                    l++;
                while (mark[r] == 1)
                    r--;
            }
            return ans;
        }
    

算法2:时间复杂度O(nlogn)。
构建结构体包含height和height在原数组中的位置
struct Node
    {
        int height;
        int index;
};
对该结构体数组按照height的值递增排序,假设排序后的数组为vec.

假设f[i] 表示数组vec[i,i+1,…]内所有height按照原来的位置顺序排列好以后的最大水量
那么f[i-1]可以在O(1)时间内计算出来:因为vec[i-1].height 小于vec[i,i+1,…]内的所有height,所以以vec[i-1].index为边界的容器高度为vec[i-1].height,最大水量只需要分别计算vec[i,i+1,…]内按原始位置排列最前面和最后面的height,取两者的较大值。即下图中,黑色是最低的,要计算以黑色为边界的容器的最大水量,只需要分别和第一个和最后一个计算,去两者较大值


image
    struct Node
    {
        int height;
        int index;
        Node(int h, int i):height(h),index(i){}
        Node(){}
        bool operator < (const Node &a)const
        {
            return height < a.height;
        }
    };
public:
    int maxArea(vector<int> &height) {
        int res = 0, n = height.size();
        if(n <= 1)return 0;
        vector<Node>vec(n);
        for(int i = 0; i < n; i++)
        {
            vec[i].index = i;
            vec[i].height = height[i];
        }
        sort(vec.begin(), vec.end());
         
        int start = vec[n-1].index, end = start;//记录已经处理完的height的原始位置的左右端点
        for(int i = n-2; i >= 0 ; i--)
        {
            start = min(start, vec[i].index);
            end = max(end, vec[i].index);
            res = max(res, max(vec[i].height*(vec[i].index - start), vec[i].height*(end - vec[i].index)));
        }
        return res;
    }


算法1:枚举容器的两个边界,时间复杂度O(n^2)。大数据超时
    int maxArea(vector<int> &height) {
        int res = 0, n = height.size();
        for(int i = 0; i < n; i++)//左边界
            for(int j = i+1; j < n; j++)//右边界
            {
                int tmp = (j-i)*min(height[i],height[j]);
                if(res < tmp)res = tmp;
            }
        return res;
    }
对上面的稍加改进,根据当前的已经计算出来的结果以及左边界的值,可以算出容器右边界的下界。不过还是超时
    int maxArea(vector<int> &height) {
        int res = 0, n = height.size();
        for(int i = 0; i < n; i++)//左边界
        {
            if(height[i] == 0)continue;
            for(int j = max(i+1, res/height[i]+i); j < n; j++)//右边界
            {
                int tmp = (j-i)*min(height[i],height[j]);
                if(res < tmp)res = tmp;
            }
        }
        return res;
    }

X. Brute Force

In this case, we will simply consider the area for every possible pair of the lines and find out the maximum area out of those.
    public int maxArea(int[] height) {
        int maxarea = 0;
        for (int i = 0; i < height.length; i++)
            for (int j = i + 1; j < height.length; j++)
                maxarea = Math.max(maxarea, Math.min(height[i], height[j]) * (j - i));
        return maxarea;
    }
wlog meaning
http://artofproblemsolving.com/wiki/index.php?title=Without_loss_of_generality
Without loss of generality is a term used in proofs to indicate that an assumption is being made that does not introduce new restrictions to the problem. For example, in the proof of Schur's Inequality, one can assume that $a \ge b \ge c$ without loss of generality because the inequality is symmetric in $a$$b$ and $c$. Without loss of generality is often abbreviated WLOG or WOLOG. Be sure not to write WLOG when you mean "with loss of generality"!
Read full article from [LeetCode]Container With Most Water, 解题报告 - 小一的专栏 - 博客频道 - CSDN.NET

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts