不一样又不一样的:木板接水

题目: 

空地上竖立着n个从左到右排列的木板,它们可以把水挡住,但溢出最边上木板的水将会流到空地上。
已知木板间距都是单位1,现给定每个木板的高度,请求出总共能接住的水量?
说明一点,这里只考虑间距(宽度)和高度,不考虑第三个维度,因此水量是平方单位。

木板高度分别是2,1,3,那么我们可以接住2*2=4平方单位的水,如下图所示。注意,中间那个木板被水淹没了。

 

木板高度分别是2,4,3,那么可以接住2*1+3*1=5平方单位的水,如下图所示。

输入:
第一行一个正整数T,表示T个测试样例;
对于每个测试样例,
输入正整数n(n<=1e5),表示n块木板;
接下来输入n个正整数,表示木板高度h(h<1e4)。

输出:

输出T行,每行一个正整数,表示每个样例能接住的最大平方单位的水量。

题解:

初始:

看到这个题目,我的第一反应就是前天的接雨水问题,所以我想到了单调栈(悲剧的开始),后来琢磨细节,第一个板子和做后一个板子无论多么短,都要存入栈。从头开始遍历寻找比栈顶高的柱子入栈,直至最后。但由于最后一根板子必须在栈里面且从后往前遍历第一根比最后一根板子大的板子也要入栈。(其实思路到这里已经出来了,但我被要使用栈给困住了,汗,没反映过来)

所以,我写了以下的代码:

int main()
{

	int T;
	cin >> T;
	for (int i = 0; i<T; ++i)
	{
		int n;
		cin >> n;
		vector<int>arr;
		for (int j = 0; j<n; ++j)
		{
			int h;
			cin >> h;
			arr.push_back(h);
		}
		
		stack<int> s;
		s.push(0);
		int len = arr.size();
		for (int i = 1; i<len; ++i)
		{
			while (s.size()>1 && arr[i] >= arr[s.top()])//倘若栈顶元素大,将栈顶元素出栈
			{
				if (s.top() == 1 && arr[1]>arr[0])//此处考虑到特殊的第一根柱子和第一个比他大的柱子
					break;
				s.pop();
			}
             //比栈顶元素小,直接放入
			s.push(i);
		}
		int ret = 0;
		while (s.size() != 1)
		{
			int end = s.top();
			s.pop();
			int start = s.top();
			int width = end - start;
			int h = min(arr[start], arr[end]);
			ret += width*h;
		}
		cout << ret << endl;
	}

	return 0;
}

看似没有问题的代码,其实隐藏着一个bug:

41 82 59 43 8 87 92 84 36 62看这组数据

经过遍历后,我们需要栈中存放的下标代表的元素有 41 82  87 92 84 62但是实际中为41 82   92  84  62,少了87;如果我们继续想通过栈储存元素进行控制,唉反正我是想了好久,没做出来。

正解:

我们刚才提过“从头开始遍历寻找比栈顶高的板子入栈,直至最后。但由于最后一根板子必须在栈里面且从后往前遍历第一根比最后一根板子大的柱子也要入栈”。 品味一下红体字,我们已经理解了题意,第一根板子和最后一根板子是没法办法忽略的。我们以一个板子为目标寻找第一个比它长的板子,找到之后,我们在一第二个板子为目标找比它长的板子。。。。。。这部分想法是正确的。但是我们要到哪里结束呢?最后的话,肯定不行。

看例子:41 82 59 43 8 87 92 84 62 36;如果按照刚才的结论92和36之间不会有板子,所以说这种办法不可取。

那倒哪呢?最长的板子。

我们可以从最长的板子的地方分为两部分,我们知道第一根板子和最后一根板子是没法办法忽略的。我们以一个板子为目标寻找第一个比它长的板子,找到之后,我们在一第二个板子为目标找比它长的板子这种思路是正确的。倘若我们将后半部分也当做前半部分来处理,就可以解决我们刚才遇到的问题。

现在,我们来整理一下思路:

先找到最长的板子,前半部分从前往后遍历到最长的板子的地方,后半部分从后往前遍历到最长板子的地方,我们以一个板子为目标寻找第一个比它长的板子,找到之后,计算存水量,我们在一第二个板子为目标找比它长的板子。。。依次往复,知道遍历结束。

代码实现:

int main()
{
     
    int T;
    cin>>T;
    for(int i=0;i<T;++i)
    {
        int n;
        cin>>n;
        vector<int>arr(n);
        int maxIndex=0;
        int max=0;
        for(int j=0;j<n;++j)
        {
            int h;
            cin>>h;
            arr[j]=h;
            if(h>max)
            {
                max=h;
                maxIndex=j;
            }
        }
        int ret=0;
        int startIndex=0;
        int start=arr[0];
        for(int i=1;i<=maxIndex;++i)
        {
            if(arr[i]>=start)
            {
                 
                ret+=((i-startIndex)*start);
                startIndex=i;
                start=arr[i];
            }
        }
        start=arr[arr.size()-1];
        startIndex=arr.size()-1;
        for(int i=startIndex-1;i>=maxIndex;--i)
        {
            if(arr[i]>=start)
            {
                ret+=((startIndex-i)*start);
                startIndex=i;
                start=arr[i];
                 
            }
        }
        cout<<ret<<endl;
    }
 
    return 0;
}

本篇博文到这里就结束了,谢谢大家的观看!

你们的 【三连】 是给Qyuan最大的肯定!

↓          ↓           ↓

注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!

如果需要练习的小伙伴,可以打开下方链接:

 

链接:https://www.nowcoder.com/questionTerminal/dee0bcab26a648ad9999bc891d460034?answerType=1&f=discussion
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值