当青训营遇上码上掘金,我们就可以开始攒青豆了
-
主题 4:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
- 在这里,我们使用三种方法对这个问题进行计算。实际上,该问题是一个非常经典的问题,它甚至有一个经典的题目《接雨水》,是必学的好题之一。
-
方法一:双指针。
我们需要给每个柱子计算它左边最高的柱子和右边最高的柱子,其中这个柱子所能承接的青豆量就是两者的最小值 - 该柱子的高度。而宽度始终为1 ,因此承接的青豆量就是 两者的最小值 - 该柱子的高度。
//双指针 int length = input.length(); int sum = 0; for(int i = 0; i < length; i++){ //在外层循环中,找到每一个柱子左边最高的柱子,并记录下来 //应该初始化为当前柱子的高度,而不是随便一个数字例如0 int l = input[i]; for(int j = 0; j < i; j++){ if(l < input[j]) l = input[j]; } //在外层循环中,找到每一个柱子右边最高的柱子,并记录下来 int r = input[i]; for(int j = length-1; j > i; j--){ if(r < input[j]) r = input[j]; } //每个柱子接的高度是左边最高柱子与右边最高柱子中最矮的柱子 与 该柱子本身之间的差值。 //不要忘记当得数大于0时,才进行计算。 if(min(l,r) - input[i] > 0) sum += min(l,r) - input[i]; } cout<<sum<<endl; -
方法二: 动态规划
在先前的双指针算法里,是遍历到每个柱子的时候,当场计算它的左边最高柱子和右边最高柱子,这样的时间复杂度就是O(n²)。我们可以在初始化时,就计算出每个柱子的左边最高柱子和右边最高柱子,这样实际上就只需要三个并列的for循环,时间复杂度也成功降低为O(n)了
int length = input.length(); int sum = 0; vector<int> l(length); vector<int> r(length); //找到左边最高柱子 l[0] = input[0]; for(int i = 1; i < length; i++){ l[i] = max(l[i-1],input[i]) } //找到右边最高柱子 r[length-1] = input[length-1]; for(int i = length-2; i >= 0; i--){ r[i] = max(r[i+1],input[i]) } for(int i = 0; i < length; i++){ if(min(l[i], r[i]) - input[i] > 0) sum += min(l[i], r[i]) - input[i] } cout<<sum<<endl; return 0; -
方法三:单调栈
以上的计算都是计算每一列来算的,而单调栈则是在利用数据结构栈的基础上,计算每一行的总和。
int length = input.length(); int sum = 0; stack<int>st; for(int i = 0; i < length; i++){ if(st.empty() || input[st.top()] > input[i]){ st.push(i); }else if(input[st.top()] == input[i]){ st.pop(); st.push(i); }else{ while(!st.empty() && input[st.top()] < input[i]){ int temp = input[st.top()]; st.pop(); if(min(input[st.top()], input[i]) - temp > 0){ sum += (min(input[st.top()], input[i]) - temp) * (i - st.top() - 1); } } st.push(i); } }
-
- 在这里,我们使用三种方法对这个问题进行计算。实际上,该问题是一个非常经典的问题,它甚至有一个经典的题目《接雨水》,是必学的好题之一。