leecode更新

This commit is contained in:
dingjiawen 2022-09-18 12:39:18 +08:00
parent 67b46dbcac
commit 82812f32ff
1 changed files with 199 additions and 0 deletions

View File

@ -0,0 +1,199 @@
package com.markilue.leecode.stackAndDeque;
import org.junit.Test;
import java.util.Deque;
import java.util.LinkedList;
import java.util.PriorityQueue;
/**
* @BelongsProject: Leecode
* @BelongsPackage: com.markilue.leecode.stackAndDeque
* @Author: dingjiawen
* @CreateTime: 2022-09-18 09:14
* @Description: TODO 力扣42题 接雨水:
* 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图计算按此排列的柱子下雨之后能接多少雨水
* @Version: 1.0
*/
public class Trap {
@Test
public void test() {
int[] height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
System.out.println(trap3(height));
}
@Test
public void test1() {
int[] height = {4, 2, 0, 3, 2, 5};
System.out.println(trap2(height));
}
@Test
public void test2() {
int[] height = {5, 4, 1, 2};
System.out.println(trap2(height));
}
@Test
public void test3() {
int[] height = {5, 5, 1, 3};
System.out.println(trap3(height));
}
@Test
public void test4() {
int[] height = {2, 0, 1, 1, 0, 1, 3, 2, 1, 2, 1};
System.out.println(trap3(height));
}
/**
* 自己的思路:这题和滑动窗口的题非常相似都是需要记录在右边比他小的值如果右边的比他大则出栈
* 因此仍然使用一个双端队列(单调队列)进行遍历遇上下一个比他大的就出栈则接雨水数就等于索引之差
* 速度击败30.41%内存击败71.84%
*
* @param height
* @return
*/
public int trap(int[] height) {
//int[元素,索引]
Deque<int[]> deque = new LinkedList<>();
//记录上一次出栈的高度
int lastHeight = 0;
int result = 0;
boolean flag = false;
for (int i = 0; i < height.length; i++) {
while (deque.size() != 0 && deque.peek()[0] <= height[i]) {
int[] poll = deque.poll();
//高度乘以宽度=蓄水量 =>(i-poll[1]-1)索引之差即宽度;这次出栈的高度-lastHeight即高度
result += (i - poll[1] - 1) * (poll[0] - lastHeight);
lastHeight = poll[0];
flag = true;
}
//防止在内部时不出站计算不了对应的雨水量的情况
//这里使用flag记录是否进入过上面的接雨水环节如果接过雨水还没出栈完,那么计算那部分的雨水量
//这里使用if即可因为flag出来以后一定遇上的是比他大的第一个数
if (deque.size() != 0 && flag && height[i] > lastHeight) {
int[] peek = deque.peek();
//高度乘以宽度=蓄水量 =>(i-poll[1]-1)索引之差即宽度;这次这次高度-lastHeight即高度
result += (i - peek[1] - 1) * (height[i] - lastHeight);
}
flag = false;
deque.push(new int[]{height[i], i});
}
return result;
}
/**
* 官方动态规划解法:只要记录左边柱子的最高高度和右边柱子的最高高度,就可以计算当前位子的雨水面积
* TODO 当前位置的雨水面积: [min(左边柱子的最高高度,右边柱子的最高高度)-当前柱子高度]*单位宽度
* 因此可以分别将左右两边柱子的最高高度分别记录在两个数组maxLeft和maxRight中这时就用到了动态规划
* 精髓在于当前位置的雨水面积的计算方式
* 时间复杂度O(n),空间复杂度O(n)
* 速度超过76.2%内存超过79.9%
*
* @param height
* @return
*/
public int trap2(int[] height) {
int n = height.length;
int[] maxLeft = new int[n];
maxLeft[0] = height[0];
for (int i = 1; i < n; i++) {
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
int[] maxRight = new int[n];
maxRight[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
int result = 0;
for (int i = 0; i < n; i++) {
result += Math.min(maxLeft[i], maxRight[i]) - height[i];
}
return result;
}
/**
* 官方单调栈解法:使用一个单调栈维护一个单调递减的栈栈顶元素小于栈低元素栈中存放索引
* 栈中必须要有两个元素才会有凹槽才能接雨水
* 速度击败39.81%,内存击败86.4%
* @param height
* @return
*/
public int trap3(int[] height) {
int n = height.length;
Deque<Integer> deque = new LinkedList<>();
int result=0;
for (int i = 0; i < n; i++) {
while (!deque.isEmpty() && height[i] > height[deque.peek()]) {
int top=deque.pop();
if(deque.isEmpty()){
//栈中必须有两个元素才会有凹槽
break;
}
int left=deque.peek();
int currWidth = i - left - 1;
int currHeight = Math.min(height[left], height[i]) - height[top];
result += currWidth * currHeight;
}
deque.push(i);
}
return result;
}
/**
* 官方进阶双指针法:思路是动态规划法为基础,同时将空间复杂度降到了O(1)
* 注意到下标 i 处能接的雨水量由 leftMax[i] rightMax[i] 中的最小值决定
* 由于数组 leftMax 是从左往右计算数组 rightMax 是从右往左计算因此可以使用双指针和两个变量代替两个数组
*
* TODO 精髓在于:如果左边比右边的小那么就算左边的高度,因为右边比左边大了那么左边的水一定可以蓄起来反之亦然
* 速度超过100%内存超过18.38%
*/
public int trap4(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
//如果左边的小那么左边的水一定可以蓄起来那么就计算左边的水
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}