研究生毕业前一日三题:第一题,立方体上面存留雨水问题(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--;		
		}
	}

两道题目整理完毕,入手点是暴力求解找出问题的解法,然后抓住问题的本质,简化解题方法。

猜你喜欢

转载自blog.csdn.net/dmulizhihui/article/details/83903380