攒青豆 | 青训营 X 码上掘金

28 阅读1分钟

当青训营遇上码上掘金

题目:有n个宽度为1的柱子,给出n个柱子的高度,排列如下,计算能够接住多少青豆。 image.png

输入: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.

image.png 该方法的时间复杂度为O(N2)O(N^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;
}

记忆化搜索

在暴力解法的基础上,增加额外的空间来存储每个位置左右两侧的最大高度。时间复杂度降低为O(N)O(N)

// 记忆化搜索
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;
	
}

双指针法

单调栈法:

我们可以遍历过程中,维护一个单调递减的栈。遍历时,当前元素小于栈顶元素时,则入栈;当前元素大于栈顶元素时,说明可以构成一个凹槽,栈顶元素出栈,判断栈顶元素两边的柱子的最小高度,作为凹槽的高度,直到碰到栈顶元素大于当前元素时,当前元素入栈。 该方法时间复杂度为O(N)O(N)

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;
}