From 82812f32ff1deea7120e1e71969140b26e5a600d Mon Sep 17 00:00:00 2001 From: dingjiawen <745518019@qq.com> Date: Sun, 18 Sep 2022 12:39:18 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../markilue/leecode/stackAndDeque/Trap.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/stackAndDeque/Trap.java diff --git a/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/Trap.java b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/Trap.java new file mode 100644 index 0000000..5f39a12 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/stackAndDeque/Trap.java @@ -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 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 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; + + } + +}