From 077ba2509dadfe0a12c2d66ea4378445334bf73c Mon Sep 17 00:00:00 2001 From: chefyuan Date: Fri, 19 Mar 2021 16:36:59 +0800 Subject: [PATCH] test --- .../单调队列单调栈/leetcode739每日温度.md | 62 ++++++++++ .../单调队列单调栈/剑指offer59队列的最大值.md | 98 +++++++++++++++ gif-algorithm/单调队列单调栈/接雨水.md | 117 ++++++++++++++++++ gif-algorithm/单调队列单调栈/最小栈.md | 78 ++++++++++++ .../单调队列单调栈/滑动窗口的最大值.md | 73 +++++++++++ gif-algorithm/单调队列单调栈/空.md | 0 6 files changed, 428 insertions(+) create mode 100644 gif-algorithm/单调队列单调栈/leetcode739每日温度.md create mode 100644 gif-algorithm/单调队列单调栈/剑指offer59队列的最大值.md create mode 100644 gif-algorithm/单调队列单调栈/接雨水.md create mode 100644 gif-algorithm/单调队列单调栈/最小栈.md create mode 100644 gif-algorithm/单调队列单调栈/滑动窗口的最大值.md delete mode 100644 gif-algorithm/单调队列单调栈/空.md diff --git a/gif-algorithm/单调队列单调栈/leetcode739每日温度.md b/gif-algorithm/单调队列单调栈/leetcode739每日温度.md new file mode 100644 index 0000000..f8a260d --- /dev/null +++ b/gif-algorithm/单调队列单调栈/leetcode739每日温度.md @@ -0,0 +1,62 @@ +### leetcode 739 每日温度 + +题目描述: + +> 请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 + +示例1: + +> 输入: temperatures = [73, 74, 75, 71, 69, 72, 76, 73] +> +> 输出:arr = [1, 1, 4, 2, 1, 1, 0, 0] + +示例2: + +> 输入:temperatures = [30,30,31,45,31,34,56] +> +> 输出:arr = [2,1,1,3,1,1,0] + +#### 题目解析 + +其实我们可以换种方式理解这个题目,比如我们 temperatures[0] = 30,则我们需要找到后面第一个比 30 大的数,也就是 31,31的下标为 2,30 的下标为 0 ,则我们的返回数组 arr[0] = 2。 + +理解了题目之后我们来说一下解题思路。 + +遍历数组,数组中的值为待入栈元素,待入栈元素入栈时会先跟栈顶元素进行对比,如果小于该值则入栈,如果大于则将栈顶元素出栈,新的元素入栈。 + +例如栈顶为69,新的元素为72,则69出栈,72入栈。并赋值给 arr,69 的索引为4,72的索引为5,则 arr[4] = 5 - 4 = 1,这个题目用到的是单调栈的思想,下面我们来看一下视频解析。 + + + +![](https://img-blog.csdnimg.cn/20210319163137996.gif) + + + +注:栈中的括号内的值,代表索引对应的元素,我们的入栈的为索引值,为了便于理解将其对应的值写在了括号中 + + + +```java +class Solution { + public int[] dailyTemperatures(int[] T) { + int len = T.length; + if (len == 0) { + return T; + } + Stack stack = new Stack<>(); + int[] arr = new int[len]; + int t = 0; + for (int i = 0; i < len; i++) { + //单调栈 + while (!stack.isEmpty() && T[i] > T[stack.peek()]){ + arr[stack.peek()] = i - stack.pop(); + } + stack.push(i); + } + return arr; + + } +} +``` + + diff --git a/gif-algorithm/单调队列单调栈/剑指offer59队列的最大值.md b/gif-algorithm/单调队列单调栈/剑指offer59队列的最大值.md new file mode 100644 index 0000000..d76b008 --- /dev/null +++ b/gif-algorithm/单调队列单调栈/剑指offer59队列的最大值.md @@ -0,0 +1,98 @@ +今天我们好好说说单调栈和单调队列。其实很容易理解,单调栈就是单调递增或单调递减的栈,栈内元素是有序的,单调队列同样也是。 + +下面我们通过几个题目由浅入深,一点一点挖透他们吧! + +## 单调队列 + +### 剑指 Offer 59 - II. 队列的最大值 + +#### 题目描述 + +请定义一个队列并实现函数 max_value 得到队列里的最大值 + +若队列为空,pop_front 和 max_value 需要返回 -1 + +**示例 1:** + +> 输入: ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"] +> [[],[1],[2],[],[],[]] +> 输出: [null,null,null,2,1,2] + +**示例 2:** + +> 输入: +> ["MaxQueue","pop_front","max_value"] +> [[],[],[]] +> 输出: [null,-1,-1] + +#### 题目解析: + +我们先来拆解下上面的示例 1 + +![队列的最大值](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/队列的最大值.6bfapy4zf1g0.png) + +其实我觉得这个题目的重点在理解题意上面,可能刚开始刷题的同学,对题意理解不够透彻,做起来没有那么得心应手,通过上面的图片我们简单了解了一下题意,那我们应该怎么做才能实现上述要求呢? + +下面我们来说一下双端队列。我们之前说过的队列,遵守先进先出的规则,双端队列则可以从队头出队,也可以从队尾出队。我们先通过一个视频来简单了解下双端队列。 + + + +![](https://img-blog.csdnimg.cn/20210319154950406.gif) + + + +我们可以用双端队列做辅助队列,用辅助队列来保存当前队列的最大值。我们同时定义一个普通队列和一个双端单调队列。普通队列就正常执行入队,出队操作。max_value 操作则返回咱们的双端队列的队头即可。下面我们来看一下代码的具体执行过程吧。 + + + +![](https://img-blog.csdnimg.cn/20210319154716931.gif) + + + + + +我们来对视频进行解析 + +1.我们需要维护一个单调双端队列,上面的队列则执行正常操作,下面的队列队头元素则为上面队列的最大值 + +2.出队时,我们需要进行对比两个队列的队头元素是否相等,如果相等则同时出队,则出队后的双端队列的头部仍未上面队列中的最大值。 + +3.入队时,我们需要维持一个单调递减的双端队列,因为我们需要确保队头元素为最大值嘛。 + +```java +class MaxQueue { + //普通队列 + Queue que; + //双端队列 + Deque deq; + public MaxQueue() { + que = new LinkedList<>(); + deq = new LinkedList<>(); + } + //获取最大值值,返回我们双端队列的对头即可,因为我们双端队列是单调递减的嘛 + public int max_value() { + return deq.isEmpty() ? -1 : deq.peekFirst(); + } + //入队操作 + public void push_back(int value) { + que.offer(value); + //维护单调递减 + while (!deq.isEmpty() && value > deq.peekLast()){ + deq. pollLast(); + } + deq.offerLast(value); + + } + //返回队头元素,此时有个细节,我们需要用equals + //这里需要使用 equals() 代替 == 因为队列中存储的是 int 的包装类 Integer + public int pop_front() { + if(que.isEmpty()) return -1; + if (que.peek().equals(deq.peekFirst())) { + deq.pollFirst(); + } + return que.poll(); + } +} +``` + +### \ No newline at end of file diff --git a/gif-algorithm/单调队列单调栈/接雨水.md b/gif-algorithm/单调队列单调栈/接雨水.md new file mode 100644 index 0000000..5e74a5b --- /dev/null +++ b/gif-algorithm/单调队列单调栈/接雨水.md @@ -0,0 +1,117 @@ +### leetcode 42 接雨水 + +这道接雨水也是一道特别经典的题目,一道必刷题目,我们也用单调栈来解决。下面我们来看一下题目吧 + +给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 + +示例 1: + +``` +输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] +输出:6 +``` + +示例 2: + +``` +输入:height = [4,2,0,3,2,5] +输出:9 +``` + +示例3: + +``` +输入:[4,3,2,0,1,1,5] +输出:13 +``` + +> 上面是由数组 [4,3,2,0,1,1,5]表示的高度图,在这种情况下,可以接 13个单位的雨水(见下图)。 + +### 题目解析: + +看了上面的示例刚开始刷题的同学可能有些懵逼,那我们结合图片来理解一下,我们就用示例3的例子进行举例,他的雨水到底代表的是什么。 + +![image-20201112163703501](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112163703501.png) + + + +上图则为我们的题目描述,是不是理解了呢?你也可以这样理解我们在地上放置了若干高度的黄色箱子,他们中间有空隙,然后我们想在他们里面插入若干蓝色箱子,并保证插入之后,这些箱子的左视图和右视图都不能看到蓝色箱子。 + +好啦题目我们已经理解了,下面我们看一下解题思路。做这个这前我们可以先去看一下我们之前做过的另一道题目每日温度。这两道题目的思路差不多,都是利用了单调栈的思想,下面我们来看一下具体思路吧。 + +这里我们也系统的说一下单调栈,单调栈含义就是栈内的元素是单调的,我们这两个题目用到的都是递减栈(相同也可以),我们依次将元素压入栈,如果当前元素小于等于栈顶元素则入栈,如果大于栈顶元素则先将栈顶不断出栈,直到当前元素小于或等于栈顶元素为止,然后再将当前元素入栈。就比如下图的4,想入栈的话则需要2,3出栈之后才能入栈,因为4大于他俩。 + +![image-20201112165115306](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112165115306.png) + +我们了解单调栈的含义下面我们来看一下接雨水问题到底该怎么做,其实原理也很简单,我们通过我们的例3来进行说明。 + +首先我们依次入栈4,3,2,0我们的数组前四个元素是符合单调栈规则的。但是我们的第五个1,是大于0的。那我们就需要0出栈1入栈。但是我们这样做是为了什么呢?有什么意义呢?别急我们来看下图。 + +![image-20201112165745965](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112165745965.png) + +上图我们的,4,3,2,0已经入栈了,我们的另一个元素为1,栈顶元素为0,栈顶下的元素为2。那么我们在这一层接到的雨水数量怎么算呢?2,0,1这三个元素可以接住的水为一个单位(见下图)这是我们第一层接到水的数量。 + +注:能接到水的情况,肯定是中间低两边高,这样才可以。 + +![image-20201112170019593](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112170019593.png) + +因为我们需要维护一个单调栈,所以我们则需要将0出栈1入栈,那么此时栈内元素为4,3,2,1。下一位元素为1,我们入栈,此时栈内元素为4,3,2,1,1。下一元素为5,栈顶元素为1,栈顶的下一元素仍为1,则需要再下一个元素,为2,那我们求当前层接到的水的数量。 + +![image-20201112171631818](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112171631818.png) + +我们是通过2,1,1,5这四个元素求得第二层的接水数为1*3=3;1是因为min(2-1,5-1)=min(1,4)得来的,大家可以思考一下木桶效应。装水的多少,肯定是按最短的那个木板来的,所以高度为1,3的话是因为5的索引为6,2的索引为2,他们之间共有三个元素(3,4,5)也就是3个单位。所以为6-2-1=3。 + +将1出栈之后,我们栈顶元素就变成了2,下一元素变成了3,那么3,2,5这三个元素同样也可以接到水。 + +![image-20201112172422535](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112172422535.png) + +这是第三层的接水情况,能够接到4个单位的水,下面我们继续出栈2,那么我们的4,3,5仍然可以接到水啊。 + +![image-20201112172634430](C:\Users\ybj\AppData\Roaming\Typora\typora-user-images\image-20201112172634430.png) + +这是我们第四层接水的情况,一共能够接到5个单位的水,那么我们总的接水数加起来,那就是 + +1+3+4+5=13。你学会了吗?别急还有视频我们,我们再来深入理解一哈。 + +![](https://img-blog.csdnimg.cn/20210319163622150.gif) + + + +### 题目代码: + +```java +class Solution { + public int trap(int[] height) { + Stack stack = new Stack(); + int water = 0; + //特殊情况 + if(height.length <3){ + return 0; + } + for(int i = 0; i < height.length; i++){ + while(!stack.isEmpty() && height[i] > height[stack.peek()]){ + //栈顶元素 + int popnum = stack.pop(); + //相同元素的情况例1,1 + while(!stack.isEmpty()&&height[popnum] == height[stack.peek()]){ + stack.pop(); + } + //计算该层的水的单位 + if(!stack.isEmpty()){ + int temp = height[stack.peek()];//栈顶元素值 + //高 + int hig = Math.min(temp-height[popnum],height[i]-height[popnum]); + //宽 + int wid = i-stack.peek()-1; + water +=hig * wid; + } + + } + //这里入栈的是索引 + stack.push(i); + } + return water; + } +} +``` + diff --git a/gif-algorithm/单调队列单调栈/最小栈.md b/gif-algorithm/单调队列单调栈/最小栈.md new file mode 100644 index 0000000..d092bcf --- /dev/null +++ b/gif-algorithm/单调队列单调栈/最小栈.md @@ -0,0 +1,78 @@ +## 单调栈 + +### leetcode 155 最小栈 + +设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 + +- push(x) —— 将元素 x 推入栈中。 +- pop() —— 删除栈顶的元素。 +- top() —— 获取栈顶元素。 +- getMin() —— 检索栈中的最小元素。 + +输入: + +> ["MinStack","push","push","push","getMin","pop","top","getMin"] +> [[],[-2],[0],[-3],[],[],[],[]] + +输出: + +> [null,null,null,null,-3,null,0,-2] + +#### 题目解析 + +感觉这个题目的难度就在读懂题意上面,读懂之后就没有什么难的了,我们在上面的滑动窗口的最大值已经进行了详细描述,其实这个题目和那个题目思路一致。该题让我们设计一个栈,该栈具有的功能有,push,pop,top等操作,并且能够返回栈的最小值。比如此时栈中的元素为 5,1,2,3。我们执行 getMin() ,则能够返回 1。这块是这个题目的精髓所在,见下图。 + +![单调栈](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/单调栈.46hlqk2xqza0.png) + +我们一起先通过一个视频先看一下具体解题思路,通过视频一定可以整懂的,我们注意观察栈 B 内的元素。 + + + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210319162722440.gif) + + + +我们来对视频进行解析 +1.我们执行入栈操作时,先观察需要入栈的元素是否小于栈 B 的栈顶元素,如果小于则两个栈都执行入栈操作。 + +2.栈 B 的栈顶元素则是栈 A 此时的最小值。则 getMin() 只需返回栈 B 的栈顶元素即可。 + +3.出栈时,需要进行对比,若栈 A 和栈 B 栈顶元素相同,则同时出栈,出栈好 B 的栈顶保存的仍为此时栈 A 的最小元素 + +#### 题目代码 + +```java +class MinStack { + //初始化 + Stack A,B; + public MinStack() { + A = new Stack<>(); + B = new Stack<>(); + } + //入栈,如果插入值,当前插入值小于栈顶元素,则入栈,栈顶元素保存的则为当前栈的最小元素 + public void push(int x) { + A.push(x); + if (B.isEmpty() || B.peek() >= x) { + B.push(x); + } + + } + //出栈,如果A出栈等于B栈顶元素,则说明此时栈内的最小元素改变了。 + //这里需要使用 equals() 代替 == 因为 Stack 中存储的是 int 的包装类 Integer + public void pop() { + if (A.pop().equals(B.peek()) ) { + B.pop(); + } + } + //A栈的栈顶元素 + public int top() { + return A.peek(); + } + //B栈的栈顶元素 + public int getMin() { + return B.peek(); + } +} +``` + +### \ No newline at end of file diff --git a/gif-algorithm/单调队列单调栈/滑动窗口的最大值.md b/gif-algorithm/单调队列单调栈/滑动窗口的最大值.md new file mode 100644 index 0000000..de77836 --- /dev/null +++ b/gif-algorithm/单调队列单调栈/滑动窗口的最大值.md @@ -0,0 +1,73 @@ +这个题目,算是很经典的类型,我们的滑动窗口主要分为两种,一种的可变长度的滑动窗口,一种是固定长度的滑动窗口,这个题目算是固定长度的代表。今天我们用双端队列来解决我们这个题目,学会了这个题目的解题思想你可以去解决一下两道题目 [剑指 Offer 59 - II. 队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/),[155. 最小栈](https://leetcode-cn.com/problems/min-stack/),虽然这两个题目和该题类型不同,但是解题思路是一致的,都是很不错的题目,我认为做题,那些考察的很细的,解题思路很难想,即使想到,也不容易完全写出来的题目,才是能够大大提高我们编码能力的题目,希望能和大家一起进步。 + +这个题目我们用到了**双端队列**,队列里面保存的则为每段滑动窗口的最大值,我给大家做了一个动图,先来看一下代码执行过程吧。 + +我们先来了解下双端队列吧,队列我们都知道,是先进先出,双端队列呢?既可以从队头出队,也可以从队尾出队,则不用遵循先进先出的规则。 + +下面我们通过一个动图来了解一下吧。 + +![](https://img-blog.csdnimg.cn/20210319154950406.gif) + + + +好啦,我们了解双端队列是什么东东了,下面我们通过一个动画,来看一下代码的执行过程吧,相信各位一下就能够理解啦。 + +我们就通过题目中的例子来表述。nums = [1,3,-1,-3,5,3,6,7], k = 3 + + + +![](https://img-blog.csdnimg.cn/20210319162114967.gif) + + + +不知道通过上面的例子能不能给各位描述清楚,如果不能的话,我再加把劲,各位看官,请接着往下看。 + +我们将执行过程进行拆解。 + +1.想将我们第一个窗口的所有值存入单调双端队列中,单调队列里面的值为单调递减的。如果发现队尾元素小于要加入的元素,则将队尾元素出队,直到队尾元素大于新元素时,再让新元素入队,目的就是维护一个单调递减的队列。 + +2.我们将第一个窗口的所有值,按照单调队列的规则入队之后,因为队列为单调递减,所以队头元素必为当前窗口的最大值,则将队头元素添加到数组中。 + +3.移动窗口,判断当前**窗口前的元素**是否和队头元素相等,如果相等则出队。 + +4.继续然后按照规则进行入队,维护单调递减队列。 + +5.每次将队头元素存到返回数组里。 + +5.返回数组 + +是不是懂啦,再回去看一遍视频吧。祝大家新年快乐,天天开心呀! + +```java +class Solution { + public int[] maxSlidingWindow(int[] nums, int k) { + int len = nums.length; + if (len == 0) { + return nums; + } + int[] arr = new int[len - k + 1]; + int arr_index = 0; + //我们需要维护一个单调递增的双向队列 + Deque deque = new LinkedList<>(); + for (int i = 0; i < k; i++) { + while (!deque.isEmpty() && deque.peekLast() < nums[i]) { + deque.removeLast(); + } + deque.offerLast(nums[i]); + } + arr[arr_index++] = deque.peekFirst(); + for (int j = k; j < len; j++) { + if (nums[j - k] == deque.peekFirst()) { + deque.removeFirst(); + } + while (!deque.isEmpty() && deque.peekLast() < nums[j]) { + deque.removeLast(); + } + deque.offerLast(nums[j]); + arr[arr_index++] = deque.peekFirst(); + } + return arr; + } +} +``` + diff --git a/gif-algorithm/单调队列单调栈/空.md b/gif-algorithm/单调队列单调栈/空.md deleted file mode 100644 index e69de29..0000000