当青训营遇上码上掘金
题目-攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
解题思路:
由题目可知,下标为i处得柱子所接到青豆的高度,取决于i处柱子左右两侧柱子中较矮一方的柱子高度(类似木桶效应:一只水桶能够装多少水,取决于它最短的那块木板),可以考虑以下几种情况(0<i<n.length):
- i的高度小于i-1,大于i+1,这种情况i处能否接到青豆取决于其右侧所有柱子中的最短长度是否存在大于i的
- i的高度大于i-1,大于i+1,这种情况无法接到青豆
- i的高度小于i-1,小于i+1,这种情况i处可以接到青豆,接到的青豆数为max(n[i+1],n[i-1])-min(n[i+1],n[i-1])
- i的高度大于i-1,小于i+1,这种情况i处能否接到青豆取决于其左侧所有柱子中的最短长度是否存在大于i的
但是,再仔细想想,不管上述哪种情况,能否接到青豆都是取决于其左右两侧是否存在比i高的柱子,如上图下标2处;若i的左右两侧存在一侧不存在比i高的柱子,则i处无法接到青豆,如上图下标4处
基于这一点,考虑使用单调栈进行解题
关于单调栈
通常对于一维数组,我们需要寻找数组中任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用使用单调栈。
单调栈就是保持栈内元素有序。单调栈里只需要存放元素的下标i就可以,如果需要使用对应的元素,直接通过数组下标进行获取。
处理流程:
先将下标0的柱子加入到栈中,然后从下标1开始遍历所有的柱子,如果当前遍历的柱子高度小于栈顶元素的高度,就把这个元素加入栈中,如果当前遍历的柱子高度大于栈顶元素的高度,则说明栈顶对应的下标处可以接到青豆,将栈顶元素弹出并标记为mid,此时的栈顶元素则是mid的左侧位置,而当前遍历的元素为mid的右侧位置,因此所接青豆高度是 min(左侧高度, 右侧高度) - mid高度
编写代码(java) :
public int qingdou(int[] height){
int sum = 0;
Stack<Integer> stack = new Stack<Integer>();
stack.push(0);
for (int i = 1; i < height.length; i++){
while (!stack.empty() && height[i] > height[stack.peek()]) {
int mid = stack.pop();
if (!stack.isEmpty()){
int left = stack.peek();
int h = Math.min(height[left], height[i]) - height[mid];
int w = i - left - 1;
sum += h * w;
}
}
stack.push(i);
}
return sum;
}
总结:
对于本题来说,使用单调栈解法相对来说不是特别好理解,但是通过此题可以了解单调栈的用法和使用场景,使用单调栈要牢记,我们需要维持顺序使单调栈保持栈内元素有序,即需要保证入栈的元素是保持递增或者递减的。如果违反该原则,则需要通过出栈元素来保持单调栈的性质。