From c5565668aa87a716f41ee22bf3a022af6539f254 Mon Sep 17 00:00:00 2001 From: chefyuan Date: Fri, 19 Mar 2021 18:50:39 +0800 Subject: [PATCH] 2021.3.19 --- .../数据结构和算法/关于栈和队列的那些事.md | 156 ++++++++++++++++++ .../关于链表的那些事.md | 0 gif-algorithm/栈和队列/删除重复的值.md | 63 +++++++ gif-algorithm/栈和队列/有效的括号.md | 109 ++++++++++++ gif-algorithm/栈和队列/栈实现队列.md | 81 +++++++++ gif-algorithm/栈和队列/移除K位数字.md | 91 ++++++++++ gif-algorithm/栈和队列/队列实现栈.md | 13 ++ 7 files changed, 513 insertions(+) create mode 100644 gif-algorithm/数据结构和算法/关于栈和队列的那些事.md rename gif-algorithm/{链表篇 => 数据结构和算法}/关于链表的那些事.md (100%) create mode 100644 gif-algorithm/栈和队列/删除重复的值.md create mode 100644 gif-algorithm/栈和队列/有效的括号.md create mode 100644 gif-algorithm/栈和队列/栈实现队列.md create mode 100644 gif-algorithm/栈和队列/移除K位数字.md create mode 100644 gif-algorithm/栈和队列/队列实现栈.md diff --git a/gif-algorithm/数据结构和算法/关于栈和队列的那些事.md b/gif-algorithm/数据结构和算法/关于栈和队列的那些事.md new file mode 100644 index 0000000..abe7b11 --- /dev/null +++ b/gif-algorithm/数据结构和算法/关于栈和队列的那些事.md @@ -0,0 +1,156 @@ +# 希望这篇文章能合你的胃口 + +大家在学习数据结构的时候应该都学习过栈和队列,对他俩的原理应该很熟悉了,栈是先进后出,队列是后进后出。下面我们通过这篇文章来帮助小伙伴们回忆一下栈和队列的那些事。 + +阅读完这篇文章你会有以下收获。 + +了解栈和队列的意义 + +了解栈和队列的实现方式 + +了解循环队列 + +学会中缀表达式转后缀表达式 + +学会后缀表达式的运算 + +## 这是栈 + +### 栈模型 + +**栈(stack)是限制插入和删除只能在一个位置上进行的表**,该位置是表的末端叫做栈的顶(top),对栈的基本操作有push(进栈)和pop(出栈),前者相当于插入,后者则是删除最后插入的元素。 + +栈的另一个名字是LIFO(先进后出)表。普通的清空栈的操作和判断是否空栈的测试都是栈的操作指令系统的一部分,我们对栈能做的基本上也就是push和pop操作。 + +注:该图描述的模型只象征着push是输入操作,pop和top是输出操作 + +![栈和队列1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/栈和队列1.1wjbrl9iudk0.png) + +下图表示进行若干操作后的一个抽象的栈。一般的模型是,存在某个元素位于栈顶,而该元素是唯一可见元素。 + +![2222](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/2222.1ksit1l8tlr4.png) + +### 栈的实现 + +因为栈是一个表,因此能够实现表的方法都可以实现栈,ArrayList和LinkedList都可以支持栈操作。 + +刷题时我们可以直接使用Stack类来进行创建一个栈。刷题时我们可以通过下列代码创建一个栈。下面两种方式哪种都可以使用。 + +``` +Deque stack = new LinkedList();//类型为TreeNode +Stack stack = new Stack(); +``` + +### 栈的应用 + +栈在现实中应用场景很多,大家在刷题时就可以注意到,很多题目都可以用栈来解决的。下面我们来说一个比较常用的情景,数字表达式的求值。 + +不知道大家是否还记得那句口令,先乘除,后加减,从左算到右,有括号的话就先算括号里面的。这是我们做小学数学所用到的。四则运算中括号也是其中的一部分,先乘除后加减使运算变的复杂,加上括号后甚之,那么我们有什么办法可以让其变的更好处理呢?波兰数学家**Jan Łukasiewicz**想到了一种不需要括号的后缀表达式,,我们也将它称之为逆波兰表示。不用数学家名字命名的原因有些尴尬,居然是因为他的名字太复杂了,所以用了国籍来表示而不是姓名。所以各位小伙伴以后给孩子起名字的时候不要太复杂啊。 + +> 扬·武卡谢维奇([波兰语](https://baike.baidu.com/item/波兰语):*Jan Łukasiewicz*,1878年12月21日[乌克兰](https://baike.baidu.com/item/乌克兰)利沃夫 - 1956年2月13日爱尔兰都柏林),[波兰](https://baike.baidu.com/item/波兰)数学家,主要致力于[数理逻辑](https://baike.baidu.com/item/数理逻辑)的研究。著名的波兰表示法逆波兰表示法就是他的研究成果。 + +#### 中缀表达式转为后缀表达式 + +我们通过一个例子,来说明如何将中缀表达式转为后缀表达式。 + +例 + +中缀:9 + ( 3 - 1 ) * 3 + 10 / 2 + +后缀:9 3 1 - 3 * + 10 2 / + + +规则 + +1.从左到右遍历中缀表达式的每个数字和符号,若是数字就输出(直接成为后缀表达式的一部分,不进入栈) + +2.若是符合则判断其与栈顶符号的优先级,是右括号或低于栈顶元素,则栈顶元素依次出栈并输出,等出栈完毕,当前元素入栈。 + +3.遵循以上两条直到输出后缀表达式为止。 + +老样子大家直接看动图吧简单粗暴,清晰易懂 + +![中缀转后缀](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/中缀转后缀.712hv6fxip40.gif) + +#### 后缀表达式计算结果 + +中缀:9 + ( 3 - 1 ) * 3 + 10 / 2=20 + +后缀:9 3 1 - 3 * + 10 2 / + + +后缀表达式的值也为20,那么我们来了解一下计算机是如何将后缀表达式计算为20的。 + +规则: + +1.从左到右遍历表达式的每个数字和符号,如果是数字就进栈 + +2.如果是符号就将栈顶的两个数字出栈,进行运算,并将结果入栈,一直到获得最终结果。 + +下面大家 继续看动图吧。 + +![后缀运算](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/后缀运算.38havvkp8k40.gif) + + + +注:为了用动图把逻辑整的清晰明了,十几秒的动图,就要整半个多小时,改进好几遍。如果觉得图片对你有帮助的话就点个赞和在看吧。 + +## 这是队列 + +### 队列模型 + +像栈一样,队列(queue)也是表。然而使用队列时插入在一端进行而删除在另一端进行,遵守先进先出的规则。所以队列的另一个名字是(FIFO)。 + +队列的基本操作是入队(enqueue):它是在表的末端(队尾(rear)插入一个元素。出队(dequeue):出队他是删除在表的开头(队头(front))的元素。 + +注:下面模型只象征着输入输出操作 + +![image-20201102213300674](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102213300674.1yvt4eulwri8.png) + +具体模型 + +![image-20201102214029660](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102214029660.7fol7xl7uz40.png) + +### 队列的实现 + +队列我们在树的层次遍历时经常使用,后面我们写到树的时候会给大家整理框架。队列同样也可以由数组和LinkedList实现,刷题时比较常用的方法是 + +``` + Queue queue = new LinkedList(); +``` + +### 循环队列 + +循环队列的出现就是为了解决队列的假溢出问题。何为假溢出呢?我们运用数组实现队列时,数组长度为5,我们放入了[1,2,3,4,5],我们将1,2出队,此时如果继续加入6时,因为数组末尾元素已经被占用,再向后加则会溢出,但是我们的下标0,和下标1还是空闲的。所以我们把这种现象叫做“假溢出”。 + +例如,我们在学校里面排队洗澡一人一个格,当你来到澡堂发现前面还有两个格,但是后面已经满了,你是去前面洗,还是等后面格子的哥们洗完再洗?肯定是去前面的格子洗。除非澡堂的所有格子都满了。我们才会等。 + +所以我们用来解决假溢出的方法就是后面满了,就再从头开始,也就是头尾相接的循环,我们把队列的这种头尾相接的顺序存储结构成为循环队列。 + +![循环队列](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/循环队列.1841k3lsp7cw.gif) + + + +我们发现队列为空时front == rear,队列满时也是front == rear,那么问题来了,我们应该怎么区分满和空呢? + +我们可以通过以下两种方法进行区分, + +1.设置标记变量flag;当front==rear 时且flag==0时为空,当front==rear且rear为1时且flag==1时为满 + +2.当队列为空时,front==rear,当队列满是我们保留一个元素空间,也就是说,队列满时,数组内还有一个空间。 + +例: + +![image-20201102222857190](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102222857190.4trq9b6gfjc0.png) + + + +![image-20201102222914762](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102222914762.34b1o0cqwse0.png) + +然后我们再根据以下公式则能够判断队列满没满了。 + +(rear+1)%queuesize==front + +queuesize,代表队列的长度,上图为5。我们来判断上面两张图是否满。(4+1)%5==0,(2+1)%5==3 + +两种情况都是满的,over。 + +注:为了用动图把逻辑整的清晰明了,十几秒的动图,就要整半个多小时,改进好几遍。如果觉得图片对你有帮助的话就点个赞和在看吧。 \ No newline at end of file diff --git a/gif-algorithm/链表篇/关于链表的那些事.md b/gif-algorithm/数据结构和算法/关于链表的那些事.md similarity index 100% rename from gif-algorithm/链表篇/关于链表的那些事.md rename to gif-algorithm/数据结构和算法/关于链表的那些事.md diff --git a/gif-algorithm/栈和队列/删除重复的值.md b/gif-algorithm/栈和队列/删除重复的值.md new file mode 100644 index 0000000..a7b36d7 --- /dev/null +++ b/gif-algorithm/栈和队列/删除重复的值.md @@ -0,0 +1,63 @@ +# 删除相邻重复对 + +今天给大家带来一个栈的经典题目,删除字符串中的相邻重复项,下面我们先来看一下题目描述。 + +给出由小写字母组成的字符串S,重复项操作会选择**两个相邻且相同**的字母,并删除他们。 + +在S上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终字符串。答案保证唯一 + +示例1: + +> 输入:“abbaca” +> 输出:”ca“ + +​ 我们在之前的文章中介绍过删除重复项的思想,当时我们介绍的重复项可能是两个或更多,今天的题目更加简单是两字母相邻且相同。这个题目我们可以使用双指针思想解决,用来判断两个字符是否相同,但是我们这个板块的主题是栈和队列,那么我们就详细介绍一下如何用栈解答这个题目。 + +## 解题思路: + +我们将字符入栈,然后新的字符入栈之前先于栈顶元素对比,判断是否和栈顶元素一致,如果一致则栈顶元素出栈,指针移到下一位,则就实现了去除重复元素。如果和栈顶元素不同或栈为空则将当前元素入栈。直至字符串遍历结束,另外我们需要注意的是栈是先进后出,最后我们元素出栈的时候,我们需要对字符串反转一下才为我们的答案。 + + + +![删除重复元素对](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\删除重复元素对.gif) + + + + + + + +```java +class Solution { + public String removeDuplicates(String S) { + Stack stack = new Stack<>(); + char[] s = S.toCharArray();//先将字符串变成字符数组 + //特殊情况 + if (S.length() == 0 || S.length() == 1) { + return S; + } + //遍历数组 + for (int i= 0; i 文章里的所有题目都是经过认真挑选的并且所有代码都经过测试大家可以放心食用。 + +题目描述 + +给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 + +有效字符串需满足: + +左括号必须用相同类型的右括号闭合。 +左括号必须以正确的顺序闭合。 +注意空字符串可被认为是有效字符串。 + +示例 1: + +> 输入: "()" +> 输出: true + +示例 2: + +> 输入: "(]" +> 输出: false + +示例 3: + +> 输入: "()]" +> 输出: false + +示例4: + +> 输入:"()[" +> +> 输出:false + + + +我这里用了两种方法进行解决,第一种是利用ArrayList,第二种是利用栈,今天主要讲 一下用栈的方法。思路很简单,我们遇到左括号就将其入栈,遇到右括号就和栈顶元素进行比较,如果是对应的则pop栈顶元素,不对应直接返回false即可。另外我们还需要考虑的就是示例3和示例4这两种情况,需要我们好好思考一下。 + +下面我们直接上动图。 + +![合法的括号](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\合法的括号.gif) + +题目代码: + +```java +class Solution { + public boolean isValid(String s) { + Stack stack = new Stack(); + //循环遍历字符串 + for(char ch : s.toCharArray()){ + //入栈的三种情况 + if (ch == '(' || ch == '[' || ch == '{') { + stack.push(ch); + } + //右括号对比,其中注意为空的情况 + if (ch == ')') { + if( stack.isEmpty() || stack.pop() != '(') { + return false; + } + } + if (ch == ']') { + if (stack.isEmpty() || stack.pop() != '[') { + return false; + } + } + if (ch == '}') { + if (stack.isEmpty() || stack.pop() != '{') { + return false; + } + } + } + //遍历结束的情况 + if (!stack.isEmpty()) { + return false; + } + return true; + } +} +``` + + + +另外我们看下另一种方法,这个方法很有趣,,我们遇到 ‘ [ ’ 时,则入栈 ' ] ' ,这样当我们遇到 ‘]’ 时只需判断栈顶元素是否和其一致即可,一致则出,继续遍历下一个,否则返回 false 。 + +这个方法有些巧妙,大家第一次看时可能不是那么容易理解,所以大家可以自己打一下,动脑子想一下代码逻辑。 + + + +```java +class Solution { + public boolean isValid(String s) { + LinkedList stack = new LinkedList<>(); + //遍历字符串 + for (char ch : s.toCharArray()) { + //遍历到左括号时右括号入栈,右括号来对比时,只需要对比是否相同。 + if (ch == '[') stack.push(']'); + else if (ch == '(') stack.push(')'); + else if (ch == '{') stack.push('}'); + //当ch为右括号时,如果为空,或不对应,则返回false; + else if (stack.isEmpty() || ch != stack.pop()) return false; + } + return stack.isEmpty();//最后判断是否为空。 + } +} +``` + diff --git a/gif-algorithm/栈和队列/栈实现队列.md b/gif-algorithm/栈和队列/栈实现队列.md new file mode 100644 index 0000000..26902e5 --- /dev/null +++ b/gif-algorithm/栈和队列/栈实现队列.md @@ -0,0 +1,81 @@ +今天给大家带来一个有意思的题目,思路很easy,但是刚刷题的小伙伴,示例理解起来可能会有点费劲,花里胡哨一大堆是啥意思啊。在之前的文章《不知道这篇文章合不合你的胃口》中写了栈是先进后出,队列是先进先出。本题让我们用两个先进后出的栈,完成一个先进先出的队列。我们应该怎么实现呢? + +废话不多说,大家看图 + +![栈实现队列](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\栈实现队列.gif) + +这就是具体思路,然后我们来看一下题目示例及官方提供的函数都是什么意思。 + +```java +class CQueue { + //创建队列 + public CQueue() { + + } + //入队 + public void appendTail(int value) { + + } + //出队 + public int deleteHead() { + + } +} +``` + +示例 1: + +```java +输入: +["CQueue","appendTail","deleteHead","deleteHead"] +[[],[3],[],[]] +输出:[null,null,3,-1] +``` + + +示例 2: + +```java +输入: +["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"] +[[],[],[5],[2],[],[]] +输出:[null,-1,null,null,5,2] +``` + +其实也很容易理解,输入有两行第一行,为执行的函数,Cqueue代表创建队列(代表我们初始化两个栈),appendTail代表入队操作(代表stackA入栈),deleteHead代表出队操作(代表我们stackB出栈) + +第二行输入代表值,分别给每个函数传入的参数,我们发现只有appendTail函数下有对应值,因为只有该函数传入参数。 + +大家可以点击该链接[剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)去实现一下,下面我们看代码。 + +```java +class CQueue { + //初始化两个栈 + Stack stack1,stack2; + public CQueue() { + stack1 = new Stack<>(); + stack2 = new Stack<>(); + + } + //入队,我们往第一个栈压入值 + public void appendTail (int value) { + stack1.push(value); + } + //出队 + public int deleteHead() { + //大家可以自己思考一下为什么if条件为stack2.isEmpty(),细节所在 + if (stack2.isEmpty()) { + //如果此时A栈没有值,则直接-1,我们可以看示例 + if (stack1.isEmpty()) { + return -1; + } + //将A栈的值,压入B栈中 + while (!stack1.isEmpty()) { + stack2.push(stack1.pop()); + } + } + return stack2.pop(); + } +} +``` + diff --git a/gif-algorithm/栈和队列/移除K位数字.md b/gif-algorithm/栈和队列/移除K位数字.md new file mode 100644 index 0000000..9152077 --- /dev/null +++ b/gif-algorithm/栈和队列/移除K位数字.md @@ -0,0 +1,91 @@ +# 移除K位数字 + +今天给大家带来一个栈的中等题目,移掉K位数字,题目很简单,但是很有趣。另外明天继续给大家带来一道栈和队列题目(困难),那么咱们的栈和队列模块就结束啦,下周开始整字符串的题目啦! + +### 题目描述 + +给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。 + +注意: + +> num 的长度小于 10002 且 ≥ k。 +> num 不会包含任何前导零。 + +示例 1 : + +> 输入: num = "1432219", k = 3 +> 输出: "1219" +> 解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。 + +示例 2 : + +> 输入: num = "10200", k = 1 +> 输出: "200" +> 解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。 + +示例 3 : + +> 输入: num = "10", k = 2 +> 输出: "0" +> 解释: 从原数字移除所有的数字,剩余为空就是0 + +题目很容易理解,而且也很容易实现,因为在示例中几乎把所有特殊情况都进行了举例,我们直接代码实现就好啦。 + +### 栈(贪心) + +下面我们来看一下用栈的解题思路,因为我们需要删除掉K位数字得到最小值,那么我们需要注意的是,删除的数字应该尽量在高位,则当前位小于前一位时,对前一位出栈,当前位入栈。大家思考一下思路是不是这样呢? + +另外我们需要注意的是,仅删除K位数字,得到最小值,比如54321,我们删除3位,得到21。但是刚才我们说当前位小于前一位时,则前一位出栈,当前位入栈,所以我们需要加上删除K位的规则。 + +废话不多说我们直接上动图,把该题吃透! + +![移除K位数字](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\移除K位数字.gif) + +PPT中的文字 + +> 这里需要注意的是,我们不需要将0入栈,因为0如果处于栈底,没有比它更小的值所以它不会被移除,我们只有在最后才有机会处理它。因为我们的010 = 10 ,首位0是需要在最后去掉的。所以我们这里可以直接不让其入栈,continue掉这次循环,也不改变K值,这样我们最后出栈处理时就不用考虑啦。这样逻辑就比官方题解好理解一些,也简洁一些。 + +> 这里需要注意的是,我们的K值还为2,我们目前仅删除2位数字,但是我们需要删除4位,但是后面的几位都是当前位大于前一位。所以我们需要在遍历结束后再移除后面最大的两位数字 + +```java +class Solution { + public String removeKdigits(String num, int k) { + //特殊情况全部删除 + if (num.length() == k) { + return "0"; + } + char[] s = num.toCharArray(); + Stack stack = new Stack<>(); + //遍历数组 + for (Character i : s) { + //移除元素的情况,k-- + while (!stack.isEmpty() && i < stack.peek() && k > 0) { + stack.pop(); + k--; + } + //栈为空,且当前位为0时,我们不需要将其入栈 + if (stack.isEmpty() && i == '0') { + continue; + } + stack.push(i); + } + while (k > 0) { + stack.pop(); + k--; + } + //这个是最后栈为空时,删除一位,比如我们的10,删除一位为0,按上面逻辑我们会返回"",所以我们让其返回"0" + if (stack.isEmpty()) { + return "0"; + } + //反转并返回字符串 + StringBuilder str = new StringBuilder(); + while (!stack.isEmpty()) { + str.append(stack.pop()); + } + return str.reverse().toString(); + } +} +``` + +这个题目也是很不错的,题目是精心挑选的,然后动图里面的例子也是精心构思过的。所以大家记得打卡呀! + diff --git a/gif-algorithm/栈和队列/队列实现栈.md b/gif-algorithm/栈和队列/队列实现栈.md new file mode 100644 index 0000000..da86d12 --- /dev/null +++ b/gif-algorithm/栈和队列/队列实现栈.md @@ -0,0 +1,13 @@ +# 队列实现栈 + +我们昨天实现了如何用两个栈实现队列,原理很简单,今天我们来实现一下如何用队列实现栈。 + +其实原理也很简单,我们利用队列先进先出的特点,每次队列模拟入栈时,我们先将队列之前入队的元素都出列,仅保留最后一个进队的元素。 + +然后再重新入队,这样就实现了颠倒队列中的元素。比如我们首先入队1,然后再入队2,我们需要将元素1出队,然后再重新入队,则实现了队列内元素序列变成了2,1。 + +废话不多说,我们继续看动图 + +![队列实现栈](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\队列实现栈.gif) + +下面我们来看一下题目代码,也是很容易理解。 \ No newline at end of file