一、503.下一个更大元素II
注意的点:
- 提到循环,就比较容易想到将两个数组拼接。
- 栈中存放的必须时数组下标而非数组值,因为在处理的过程中需要用到下标。
以下是代码部分:
public class 下一个更大元素II503 {
//思路:再拼接一个nums数组,然后就构成了一个循环
public int[] nextGreaterElements(int[] nums) {
//构造一个新的数组,是原数组的两倍
int[] nums2 = new int[nums.length*2];
//踩坑,这里是 <nums.length
for (int i = 0; i < nums.length; i++) {
nums2[i] = nums[i];
nums2[i + nums.length] = nums[i];
}
int[] result = new int[nums.length];
int[] result2 = new int[nums.length*2];
for (int i = 0; i < result2.length; i++) {
result2[i] = -1;
}
Stack<Integer> s = new Stack<>();
for (int i = 0; i < nums2.length; i++) {
while (!s.isEmpty() && nums2[s.peek()] < nums2[i]){
//栈中必须存下标,因为这里会用到
result2[s.peek()] = nums2[i];
s.pop();
}
s.push(i);
}
//将result2中的前半部分赋给result
for (int i = 0; i < result.length; i++) {
result[i] = result2[i];
}
return result;
}
}
二、42. 接雨水
注意的点
- 可用动态规划、单调栈、双指针三种方式实现。
- 动态规划和双指针思路相同,都是寻找每一列两侧的最大值,从而记录该列最终存储的数量;而单调栈则是寻找左右两侧第一个大的值,记录的仅仅只是暂时该列存储的雨水数量,而非最终的雨水数量,可以看作通过行的方式求解。
以下是代码部分:
public class 接雨水42 {
//动态规划
//思路:维持两个dp数组,一个从左向右看取最大值;一个从右向左看取最大值
public int trap(int[] height) {
//定义dp数组
int[] left = new int[height.length];
int[] right = new int[height.length];
//初始化,由于非负,所以最小值设为0
int leftMax = 0;
int rightMax = 0;
//遍历
for (int i = 0; i < left.length; i++) {
leftMax = Math.max(leftMax, height[i]);
left[i] = leftMax;
}
for (int i = right.length-1; i >= 0; i--) {
rightMax = Math.max(rightMax, height[i]);
right[i] = rightMax;
}
int result = 0;
//取两个dp数组中的最小值,再减去当列的原高度作为第i列可以储水的高度
for (int i = 0; i < height.length; i++) {
result += Math.min(left[i], right[i]) - height[i];
}
return result;
}
//单调栈
//思路:使用单调栈找到右边第一个大于当前列的值,此时形成凹槽,可以储水。相比动态规划,单调栈更类似于从行的角度解决问题(每次求解的并不是该列真正可以储水的量)
public int trap2(int[] height) {
Stack<Integer> s = new Stack<>();
int weight = 0;
int high = 0;
int tmp = 0;
int result = 0;
for (int i = 0; i < height.length; i++) {
//维持一个递减序列
//当发现凹槽时,就进行一个储水值的计算
while (!s.isEmpty() && height[s.peek()] < height[i]){
//首先要弹出当前的栈头,因为要在栈头下边寻找它的左边界(左右两个比栈头大的值形成了一个凹槽)
tmp = s.pop();
//这里要判断弹出栈顶元素后,当前栈是否为空
//弹出为空,证明左边没有更大的值,所以储存不了水
if(s.isEmpty())
break;
//求凹槽的高度
//这里注意减的是 height[tmp] ,而不是tmp
high = Math.min(height[i], height[s.peek()]) - height[tmp];
//求凹槽的宽度,这里就会用到下标,所以要在栈中存储下标。多减一个 1 是因为要求两端中间的宽度,不包括两端
weight = i - s.peek() - 1;
//记录储水的值
result += high * weight;
}
//入栈操作,每个元素都会进行
s.push(i);
}
return result;
}
//双指针
//思路:其实就是在动态规划的基础上进行一个空间上的一个减少,只需要O(1)的空间
public int trap3(int[] height) {
//这里必须初始化成第一个和最后一个值,否则在刚开始的时候left、right还是0,比较就有问题了
int left = height[0];
int right = height[height.length-1];
int result = 0;
int i = 0;
int j = height.length - 1;
//踩坑: 这里比较必须是 i<=j 而不能是 i<j。因为如果不判断 = 最后有可能会少判断一列的情况
while (i <= j){
//如果左边的最高值小于右边的,则移动左边的
while (left <= right && i <= j){
//如果当前高度大于维持的左边最高,则更新,同时也储蓄不了水
if(height[i] >= left){
left = height[i];
i++;
}
//否则,可以储水。储水的值就是动态规划中的公式
else {
result += Math.min(left, right) - height[i];
i++;
}
}
//踩坑:直接就复制过来了,忘了i、j互换了
while (left >= right && i <= j){
if(height[j] >= right){
right = height[j];
j--;
}
else {
result += Math.min(left, right) - height[j];
j--;
}
}
}
return result;
}
}