503.下一个更大元素II 42.接雨水
503.下一个更大元素II
力扣题目链接(opens new window)
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
- 输入: [1,2,1]
- 输出: [2,-1,2]
- 解释: 第一个 1 的下一个更大的数是 2;数字 2 找不到下一个更大的数;第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
提示:
- 1 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
思路
思路:单调栈
将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。
时间复杂度:O(n)
空间复杂度:O(n)
代码如下
public int[] nextGreaterElements(int[] nums) {if (nums == null)// 边缘条件判断return null;int[] doubleNums = new int[2 * nums.length];// 拼接两个Nums数组for (int i = 0; i < doubleNums.length; i++) {if (i < nums.length) {doubleNums[i] = nums[i];} else {doubleNums[i] = nums[i - nums.length];}}int[] result = new int[nums.length];// nums的结果集int[] doubleResult = new int[doubleNums.length];// doubleNums的结果集for (int i = 0; i < doubleNums.length; i++)doubleResult[i] = -1;Stack<Integer> stack = new Stack<>();stack.push(0);for (int i = 1; i < doubleNums.length; i++) {// 填充doubleResult数组Integer top = stack.peek();while (doubleNums[i] > doubleNums[top]) {top = stack.pop();doubleResult[top] = doubleNums[i];if (stack.isEmpty())break;top = stack.peek();}stack.push(i);}for (int i = 0; i < nums.length; i++) {// doubleResult前一半元素存储在result中result[i] = doubleResult[i];}return result;
}
优化
不扩充nums数组,在遍历的过程中模拟走了两遍nums数组
在写下面代码时,有个问题。在遍历的过程中模拟走了两遍nums数组,为什么result不定义成二倍nums长度呢?
可以看下这两行代码。result的下标来自栈顶弹出元素,这毫无疑问。之前做过的几个单调栈题目也都是这么操作的top = stack.pop(); result[top] = nums[i % nums.length];
加入栈的元素代码如下。可以看出,加入栈的元素范围被限制在0~nums.length-1之间,所以result的大小定义为nums长度即可
stack.push(i % nums.length);
代码如下
public int[] nextGreaterElements(int[] nums) {if (nums == null)// 边缘条件判断return null;int[] result = new int[nums.length];// nums的结果集for (int i = 0; i < nums.length; i++)result[i] = -1;Stack<Integer> stack = new Stack<>();stack.push(0);for (int i = 1; i < 2 * nums.length; i++) {// 填充doubleResult数组Integer top = stack.peek();while (nums[i % nums.length] > nums[top]) {top = stack.pop();result[top] = nums[i % nums.length];if (stack.isEmpty())break;top = stack.peek();}stack.push(i % nums.length);}return result;
}
42.接雨水
力扣题目链接(opens new window)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
- 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
- 输出:6
- 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
- 输入:height = [4,2,0,3,2,5]
- 输出:9
思路:暴力算法
暴力算法(双指针)
求雨水的体积首先明确按照行来计算还是按照列来计算
暴力算法倾向于按照列来计算。那么以列为单位,每一列的宽度一定是1,只要求出雨水高度即可
如图
列中雨水的高度取决于左侧最高的列,右侧最高的列中,较小值的高度
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍
那么此时雨水的体积等于(雨水高度 - 列高度) * 列宽 = (雨水高度 - 列高度)
时间复杂度为O(n^2)
空间复杂度为O(1)
代码如下
public static int trap(int[] height) {if (height == null)// 边缘条件判断return 0;int rainNum = 0;for (int i = 1; i < height.length - 1; i++) {int leftHeight = 0;int rightHeight = 0;for (int j = 0; j < i; j++) {// 求左侧最高列if (height[j] > leftHeight)leftHeight = height[j];}for (int j = i + 1; j < height.length; j++) {// 求右侧最高列if (height[j] > rightHeight)rightHeight = height[j];}// 求当前列的雨水体积if (Math.min(leftHeight, rightHeight) - height[i] > 0)rainNum = rainNum + Math.min(leftHeight, rightHeight) - height[i];}return rainNum;
}
优化:双指针算法
优化(双指针算法)
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍
这样导致时间复杂度为O(n^2)
我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算。
对于当前位置而言,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
时间复杂度为O(n)
空间复杂度为O(n)
代码如下
if (height == null)// 边缘条件判断return 0;int[] maxLeft = new int[height.length];// 保存当前下标i,height[i]左侧最大元素
maxLeft[0] = height[0];
for (int i = 1; i < height.length; i++) {maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}int[] maxRight = new int[height.length];// 保存当前下标i,height[i]右侧最大元素
maxRight[height.length - 1] = height[height.length - 1];
for (int i = height.length - 2; i >= 0; i--) {maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}int rainNum = 0;
for (int i = 1; i < height.length - 1; i++) {if (Math.min(maxLeft[i], maxRight[i]) - height[i] > 0) {rainNum = rainNum + Math.min(maxLeft[i], maxRight[i]) - height[i];}}
return rainNum;
思路:单调栈
思路:单调栈
单调栈题目适用于寻找一维数组中,任一个元素的右边或者左边第一个比自己大或者小的元素的位置
本题目需要寻找一个元素,左侧和右侧最大元素来计算雨水面积
使用单调栈有几个点需要搞清楚
1.单调栈是按照行方向来计算雨水,如图
2.使用单调栈内元素的顺序
从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
因为一旦弹出元素,弹出元素下标mid对应的height[mid]就是凹槽的高度
此时栈顶的元素为凹槽左侧边界下标
而要入栈的元素为凹槽右侧边界下标如图
3.遇到相同高度的柱子怎么办。
一旦遇到相同高度的柱子,将其弹出,需要使用最右边的柱子来计算宽度如图
代码如下
// 时间复杂度为O(n)
// 空间复杂度为O(n)
public static int trap(int[] height) {if (height == null)// 边缘条件判断return 0;int rainNum = 0;Stack<Integer> stack = new Stack<>();stack.push(0);for (int i = 1; i < height.length; i++) {Integer top = stack.peek();if (height[i] >= height[top]) {Integer mid = 0;// 取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid]while (height[i] > height[top]) {mid = stack.pop();if (stack.isEmpty())break;top = stack.peek();rainNum = rainNum + (Math.min(height[top], height[i]) - height[mid]) * (i - top - 1);}}stack.push(i);}return rainNum;
}