当青训营遇上码上掘金
题目:有n个宽度为1的柱子,给出n个柱子的高度,排列如下,计算能够接住多少青豆。
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
输入输出
java中该题的输入输出为
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNextInt()) {
int n = in.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; i ++) {
arr[i] = in.nextInt();
}
// 调用下面相应的方法
System.out.println(solution(arr));
}
}
暴力解法
要找到每一列能够接住多少豆子,就必须要找到当前位置左右两侧比当前位置高的柱子。以当前位置为基准,找到当前位置左侧和右侧高于当前位置,且最高的柱子。短板效应,选择较低的柱子。以下标为2的列为例,左侧最高的是下标为0高度为5,右侧最高的是下标为4高度为4,因此下标为2的列最多可接豆子为4-2=2.
该方法的时间复杂度为,因为每遍历一个位置,都要遍历左右两侧寻找最高的柱子。
//暴力解法
public static int solution1(int[] arr) {
int result = 0;
for(int i = 1; i < arr.length-1; i ++) {
int left = arr[i];
int right = arr[i];
// 找到左侧高于当前位置,且最高的柱子
for(int x = i - 1; x >= 0; x --) {
left = arr[x] > left ? arr[x] : left;
}
//找到右侧高于当前位置,且最高的柱子
for(int y = i + 1; y < arr.length; y ++) {
right = arr[y] > right ? arr[y] : right;
}
// 计算当前位置接豆子数
if(left > arr[i] && right > arr[i]) {
int min = left < right ? left : right;
result += min - arr[i];
}
}
return result;
}
记忆化搜索
在暴力解法的基础上,增加额外的空间来存储每个位置左右两侧的最大高度。时间复杂度降低为
// 记忆化搜索
public static int solution2(int[] arr) {
int result = 0;
int[] leftMax = new int[arr.length];
int[] rightMax = new int[arr.length];
int max = arr[0];
// 左侧最大值
for(int i = 0; i < arr.length; i ++) {
max = arr[i] > max ? arr[i] : max;
leftMax[i] = max;
}
max = arr[arr.length - 1];
// 右侧最大值
for(int j = arr.length - 1; j >= 0; j --) {
max = arr[j] > max ? arr[j] : max;
rightMax[j] = max;
}
// 计算每个位置的豆子数
for(int k = 0; k < arr.length ; k ++) {
if(leftMax[k] > arr[k] && rightMax[k] > arr[k]) {
int min = leftMax[k] > rightMax[k] ? rightMax[k] : leftMax[k];
result += min - arr[k];
}
}
return result;
}
双指针法
单调栈法:
我们可以遍历过程中,维护一个单调递减的栈。遍历时,当前元素小于栈顶元素时,则入栈;当前元素大于栈顶元素时,说明可以构成一个凹槽,栈顶元素出栈,判断栈顶元素两边的柱子的最小高度,作为凹槽的高度,直到碰到栈顶元素大于当前元素时,当前元素入栈。 该方法时间复杂度为
public static int solution(int[] arr) {
LinkedList<Integer> stack = new LinkedList<>();
int result = 0;
for(int i = 0; i < arr.length; i ++) {
while(stack.size() != 0 && arr[stack.peek()] < arr[i]) {
int mid = stack.pop();
if(stack.size() == 0) {
break;
}
int left = stack.peek();
int area = (arr[left] < arr[i] ? (arr[left]-arr[mid]) * (i - left - 1): (arr[i]-arr[mid]) * (i - left - 1));
result += area;
}
stack.push(i);
}
return result;
}