栈和队列---算法题目
1.设计一个有getMin功能的栈
1.解题思路
方案一:
push:将每次插入的新值和stackMin的栈顶元素比较,如果新值较小就插入到stackMin,否则什么也不干
pop:stackData出栈的元素如果是栈中最小元素,则stackMin对应的元素也要出栈
方案二:
push:将方案二“什么也不干”这件事改为重复插入stackMin栈顶元素
pop:因为push操作重复的插入,则pop时不需要比较stackData出栈元素是否是stackMin栈顶元素,两个栈 同步出栈即可。
2.遇到的问题
- 方案一为什么能保证stackMin栈顶元素是此时stackData所有元素的最小值
- 因为压栈的时候stackMin总是与插入的值比较,保持栈顶是最小值
- 方案二为什么要同步压入
- 因为可以同步出栈
3.代码
代码:
//方案一
public class MyStack1{private Stack<Integer> stackData;private Stack<Integer> stackMin;public MyStack1(){this.stackData = new Stack<>();this.stackMin = new Stack<>();}public void push(int newNum){if (stackMin.isEmpty()) {stackMin.push(newNum);}else{if (newNum <= getMin()) {stackMin.push(newNum);}}stackData.push(newNum);}public int pop(){if (stackData.isEmpty()) {throw new RuntimeException("Stack is Empty");}int value = stackData.pop();if (value == getMin()) {stackMin.pop();}return value;}public int getMin(){if (stackMin.isEmpty()) {throw new RuntimeException("Stack is Empty");}return stackMin.peek();}
}//方案二
public class MyStack2{private Stack<Integer> stackData;private Stack<Integer> stackMin;public MyStack2(){this.stackData = new Stack<>();this.stackMin = new Stack<>();}public void push(int newNum){if (stackMin.isEmpty()) {stackMin.push(newNum);}else{if (newNum < getMin()) {stackMin.push(newNum);}else{//重复压入最小值stackMin.push(getMin());}}stackData.push(newNum);}public int pop(){if (stackData.isEmpty()) {throw new RuntimeException("Stack is Empty");}stackMin.pop();return stackData.pop();}public int getMin(){if (stackMin.isEmpty()) {throw new RuntimeException("Stack is Empty");}return stackMin.peek();}
}
4.收获
一个栈不能解决的问题,那就用两个栈
2.由两个栈组成的队列
编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)
1.解题思路
先将数压入stackPush栈,再将stackPush栈中的数出栈到stackPop
期间需满足:
- 如果stackPop栈不为空,stackPush不能出栈到stackPop
- 如果stackPop能出栈,则要一次性出完
2.遇到的问题
3.代码
代码
public class TwoStacksQueue{public Stack<Integer> stackPush;public Stack<Integer> stackPop;public TwoStacksQueue(){stackPush = new Stack<Integer>();stackPop = new Stack<Integer>();}public void add(int pushInt){stackPush.push(pushInt);}public int poll(){if (!stackPop.isEmpty()) {return stackPop.pop();}else{if (stackPush.isEmpty()) {throw new RuntimeException("Queue is empty!!");}else{while (!stackPush.isEmpty()) {stackPop.push(stackPush.pop());}}return stackPop.pop();}}public int peek(){if (!stackPop.isEmpty()) {return stackPop.peek();}else{if (stackPush.isEmpty()) {throw new RuntimeException("Queue is empty!!");}else{while (!stackPush.isEmpty()) {stackPop.push(stackPush.pop());}}return stackPop.peek();}}}
4.收获
抓住了栈和队列的特点
栈:先进后出
队列:先进先出
3.如何仅用递归函数和栈操作逆序一个栈
1.解题思路
getLastAndRemove递归方法流程:
reverse递归方法流程:
2.遇到的问题
3.代码
代码
public class Solution{public void reverse(Stack<Integer> stack){if (stack.isEmpty()) {return;}int i = getLastAndRemove(stack);reverse(stack);stack.push(i);}private int getLastAndRemove(Stack<Integer> stack){int res = stack.pop();if (stack.isEmpty()) {return res;}int last = getLastAndRemove(stack);stack.push(res);return last;}}
4.收获
要理解递归函数,必须先弄清楚它的base case,即递归最深的一层,看它的返回值和上一次递归的前后操作(即栈保存的信息有哪些)
4.猫狗队列
给定Pet、Dog、Cat类结构,不能改变原有类结构
1.解题思路
新建一个类,属性依赖Pet,另外增加时间戳
2.遇到的问题
- 不能修改原来的类结构
- 强制类型转换
3.代码
代码
public class Pet{private String type;public Pet(String type){this.type = type;}public String getPetType(){return this.type;}
}public class Dog extends Pet{public Dog(){super("dog");}
}public class Cat extends Pet{public Cat(){super("cat");}
}public class PetEnterQueue{private Pet pet;private long count;public PetEnterQueue(Pet pet, long count){this.pet = pet;this.count = count;}public String getPet(){return this.pet;}public long getCount(){return this.count;}public String getPetEnterType(){return this.pet.getType();}
}public class CatDogQueue{private Queue<PetEnterQueue> dogQ;private Queue<PetEnterQueue> catQ;private long count;public DogCatQueue(){dogQ = new LinkedList<>();catQ = new LinkedList<>();count = 0;}public void add(Pet pet){if (pet.getType.equals("dog")) {dogQ.add(new PetEnterQueue(pet, count++));}else if (pet.getType.equals("cat")) {catQ.add(new PetEnterQueue(pet, count++));}else{throw new RuntimeException("No such pet type");}}public Pet pollAll(){while (!dogQ.isEmpty && !catQ.isEmpty()) {if (dogQ.peek().getCount() > catQ.peek().getCount()) {dogQ.poll();}else{catQ.poll();}}if (dogQ.isEmpty()) {while (!catQ.isEmpty()) {catQ.poll();}}else{while (!dogQ.isEmpty()) {dogQ.poll();}}}public Dog pollDog(){if (!dogQ.isEmpty()) {return (Dog)dogQ.poll().getPet();}else{throw new RuntimeException("Dog queue is empty!!");}}public Cat pollCat(){if (!catQ.isEmpty()) {return (Cat)catQ.poll().getPet();}else{throw new RuntimeException("Cat queue is empty!!");}}public boolean isEmpty(){return catQ.isEmpty() && dogQ.isEmpty();}public boolean isDogQueueEmpty(){return dogQ.isEmpty();}public boolean isCatQueueEmpty(){return catQ.isEmpty()}
}
4.收获
实现特殊的数据结构和算法设计
5.用一个栈实现另一个栈的排序
1.解题思路
将stack的栈顶元素(假设为cur)与help栈顶 元素比较,如果大于,直接压栈到help,否则,一直将help的元素压入stack,直到cur找到在help中合适的位置
2.遇到的问题
3.代码
代码:
public class Solution{public void sortStackByStack(Stack<Integer> stack){Stack<Integer> help = new Stack<>();while(!stack.isEmpty()){int cur = stack.pop();while (!help.isEmpty() && cur < help.peek()) {stack.push(help.pop());}help.push(cur);}}
}
4.收获
6.用栈来求汉诺塔问题
1.解题思路
递归的方法
base case(塔数为1(依次是N…1)):
左到中
1步
中到左
1步
中到右
1步
右到中
1步
左到右
- 从左到中 1步
- 从中到右 1步
右到左
- 从右到中 1步
- 从中到左 1步
移动的情况分类:
左到中
要将N个塔从左移动到中须经历以下3个步骤
1.将1~N-1个塔从左移动到右 交给递归
2.将N从左移动到中 1步(因为塔数为一个)
3.将1~N-1个塔从右移动到中 交给递归
中到左
中到右
右到中
左到右
分为5个步骤(塔不能直接从左移动到右,必须经历中间):
1.将1~N-1个塔从左移动到右 递归
2.将N从左移动到中 1步
3.将1~N-1个塔从右移动到左 递归
4.将N从中移动到右 1步
5.将1~N-1个塔从左移动到右 递归
右到左
栈的方法
- 遵循两个原则:
- 从一个栈移到另外一个栈,遵循小压大原则
- 如果上一步是从a栈移入到b栈,则下一步不能从b栈移入到a栈,也不能一步移入到c栈,因此下一步总是确定的
- 遵循两个原则:
2.遇到的问题
将重复情况归为同一大类
3.代码
递归方法:
public class Solution{public int hanoiProblem1(itn num, String left, String mid, String right){if (num < 1) {return 0;}return process(num, left, mid, right, left, right);}private int process(int num, String left, String mid, String right, String from, String to){// base caseif (num == 1) {if (from.equals("mid") || to.equals("mid")) {System.out.println("Move 1 from " + from + " to " + to);}else{System.out.println("Move 1 from " + from + " to " + mid);System.out.println("Move 1 from " + mid + " to " + to);return 2;}}if (from.equals("mid") || to.equals("mid")) {String another = (from.equals("left") || to.equals("left")) ? right : left;int p1 = process(num - 1, from, mid, to, from, another);int p2 = 1;System.out.println("Move " + num + " from " + from + " to " + to);int p3 = process(num - 1, from, mid, to, another, to);return p1 + p2 + p3;}else{int p1 = process(num - 1, from, mid, to, left, right);int p2 = 1;System.out.println("Move " + num + " from " + from + " to " + mid);int p3 = process(num - 1, from, mid, to, right, left);int p4 = 1;System.out.println("Move " + num + " from " + mid + " to " + to);int p5 = process(num - 1, from, mid, to, left, right);return p1 + p2 + p3 + p4 + p5;}}
}
栈方法:
public enum Action {No, LToM, MToL, MToR, RToM
}public class Solution{public int hanoiProblem2(int num, String left, String mid, String right) {Stack<Integer> lS = new Stack<>();Stack<Integer> mS = new Stack<>();Stack<Integer> rS = new Stack<>();Action[] record = {Action.No};lS.push(Integer.MAX_VALUE);mS.push(Integer.MAX_VALUE);rS.push(Integer.MAX_VALUE);for (int i = 1; i <= num; i++) {ls.push(i);}int step;while () {//第一次循环 第一步必是从lStack到rStackstep += fStackToTStack(record, Action.MToL, Action.LToM, lS, mS, String left, String mid);step += fStackToTStack(record, Action.LToM, Action.MToL, mS, lS, String mid, String left);step += fStackToTStack(record, Action.RToM, Action.MToR, mS, rS, String mid, String right);step += fStackToTStack(record, Action.MToR, Action.RToM, rS, mS, String right, String mid);}return step;}private int fStackToTStack(int[] record, Action preNoAct, Action nowAct, Stack fStack, Stack tStack, String from, String to) {//不能走回路 && 遵循小压大if (record[0] != preNoAct && fStack.peek() < tStack.peek()) {tStack.push(fStack.pop());record[0] = nowAct;System.out.println("Move " + tStack.peek() + "from " + from + "to " + to);return 1;}return 0;}
}
4.收获
利用栈的特性解决某些领域的问题-----单调栈
7.生成窗口最大值数组
题目详情见 pdf P.19
1.解题思路
- 生成一个双端队列
- 该队列存放数组下标,数组下标对应的值降序[大…小(等于)]
- 向队列添加下标的规则
- 队列为空 直接添加
- 队列不为空 始终将下标对应的值arr[i]添加到队头
- 如果队列头下标过期,将该下标移除
- 判断下标是否过期
- j == i - w j为队列中的下标 i为遍历变量 w为窗口大小
2.遇到的问题
- 为什么使用双向队列
- 因为窗口问题经常使用双向队列作为辅助数据接口
3.代码
public class Solution {public int[] getMaxWindow(int[] arr, int w) {if (arr == null || w < 1 ||arr.length < w) {return null;}LinkedList<Integer> qmax = new LinkedList<>();int[] res = new int[arr.length - w + 1];int index = 0;for (int i = 0; i < arr.length; i++) {//比较arr[i] 和 queue队头下标j arr[j] 的值 将i存放到正确的位置while (!queue.isEmpty && arr[queue.peekLast()] <= arr[i]) {qmax.pollLast();}qmax.addLast(i);//下标过期if (qmax.peekFirst() == i - w) {qmax.pollFirst();}//从w - 1的位置开始形成窗口if (i >= w - 1) {res[index++] = arr[qmax.peekFirst()];}}return res;}}
4.收获
使用双向队列辅助移动窗口问题
8.构造数组的MaxTree
1.解题思路
- 构造MaxTree按下列两条规则即可构造出
一颗二叉树
- 数组中某节点的父节点在左边第一个节点值比该节点值大和右边第一个节点值比该节点值大的这两个节点中取节点值较小的那个
- 如果两边都没有比该节点值大的,则该节点为root节点
- 怎么证明是构造一棵树
- 怎么证明是一颗二叉树
2.遇到的问题
- 如何实现规则一
- 利用栈
- 如何拼接节点
- if和else if分支的条件模糊
3.代码
class Node {public int value;public Node left;public Node right;public Node(int value){this.value = value;}
}public class Solution {public Node getMaxTree(int[] arr) {//将int型数组转换为Node型数组Node[] nArr = new int[arr.length];for (int i = 0; i < arr.length; i++) {nArr = new Node(arr[i]);}//求左边第一个该节点值大的节点HashMap<Node, Node> lFristMax = new HashMap<>();Stack<Node> stack = new Stack<>();for (int i = 0; i < nArr.length; i++) {//将nArr[i]按正确的位置放入stack 栈顶到栈底升序//如果pop某个节点 则保存该节点的左边第一个比它大的节点放入lFristMaxwhile (!stack.isEmpty() && stack.peek().value < nArr[i].value) {//找到正确的位置 可能会pop出节点popStackSetMap(stack, lFristMax);}stack.push(nArr[i]);}//清除栈 同时找节点的左边第一个比它大的节点放入lFristMaxwhile(!stack.isEmpty()) {popStackSetMap(stack, lFristMax);}//求右边第一个该节点值大的节点HashMap<Node, Node> rFristMax = new HashMap<>();for (int i = nArr.length - 1; i >= 0; i--) {while (!stack.isEmpty() && stack.peek().value < nArr[i].value) {popStackSetMap(stack, rFristMax);}stack.push();}while(!stack.isEmpty()) {popStackSetMap(stack, rFristMax);}//将nArr中的节点拼接成题目要求的树Node head = null;for (int i = 0; i < nArr.length; i++) {Node cur = nArr[i];Node left = lFristMax.get(cur);Node right = rFristMax.get(cur);if (left == null && right == null) {head = cur;}else if (left == null) {if (right.left != null) {right.left = cur;}//可能是根节的左右孩子 否则其他节点有且只有一个孩子即左孩子else {right.right = cur;}}else if (left == null) {if (left.left != null) {left.left = cur;}else{left.right = cur;}}else {Node parent = left.value < right.value ? left : right;if (parent.left != null) {parent.left = cur;}else {parent.right = cur;}}}return head;}private void popStackSetMap(Stack<Node> stack, HashMap<Node, Node> map) {//pop的同时找到左边第一个比它大的节点 并放入lFirstMax rFirstMax亦是如此Node cur = stack.pop();if (!stack.isEmpty()) {map.put(cur, stack.peek());}else {map.put(cur, null);}}}
4.收获
学会了如何用栈求左边和右边第一个比他大的节点
9.求最大子矩阵的大小
1.解题思路
本题利用数形结合的方法进行求解
- 创建一个height[]数组,用来从放从上到下有多少个连续的1
- 对矩阵每行都求出height
- 那么如何从height中求出最大矩阵呢
- 我们可以将height看成直方图 在直方图求出最大面积
- 求出最大面积也就是求出height[j]向左右能扩展到哪,可以利用栈,遵循大压小的原则(因为大压小可以求出下方小的部分能最远扩展到哪,因为i一直增加)利用下标求出扩展宽度
- 可以得出一个公式 扩展宽度 = i - k - 1 i代表遍历height,j代表出栈元素,k代表出栈后栈顶元素
2.遇到的问题
遍历height数组,变量i、j、k所代表的含义
3.代码
public class Solution {public int maxRecSize(int[][] map) {if (map == null || map.length == 0 || map[0].lenght == 0) {return 0;}int res = 0;//求heights[] 其代表的是向上连续的1有多少个//时间复杂度: M * N (M、N分别代表map的行和列)int[] heights = new int[map[0].length];for(int i = 0; i < map.length; i++) {for(int j = 0; j < heights.length; j++) {heights[j] = map[i][j] == 0? 0 : heights[j] + 1;}//求该行及其上方形成的最大矩阵res = Math.max(res, maxRecFromBottom(heights));}return res;}private int maxRecFromBottom(int[] heights) {if (heights == null || heights.length == 0) {return 0;}Stack<Integer> stack = new Stack<>();int area = 0;//遵守入栈规则 大压小//否则出栈//出栈的同时求左右扩展面积int i;for ( i = 0; i < heights.length; i++) {while (!stack.isEmpty() && heights[i] <= stack.peek()) {int j = stack.pop();int k = stack.isEmpty() ? -1 : stack.peek();int curArea = (i - k - 1) * heights[j];area = Math.max(area, curArea);}stack.push(i);}//清空栈while (!stack.isEmpty()) {int j = stack.pop();int k = stack.isEmpty() ? -1 : stack.peek();int curArea = (i - k - 1) * height[j];area = Math.max(area, curArea);}return area;}}
4.收获
- 本题利用数形结合的方法
- 可以利用栈遵循某种规则
10.最大值减去最小值小于或等于num的字数组数量
1.解题思路
max - min <= num (本来是比较下标对应的值,由于书写方便, 我就直接写出下标了)
令 res = max - min
窗口无非就是扩容(j ++)和缩小(i++), 这两种情况引起min和max的变化如下
扩容
max
- 不变
- 变大
min
- 不变
- 变小
可见,无论max和min怎么组合,res要么增大,要么不变
故如果res > num(即不符合条件),则arr[i…j + k]必不符合要求
则此时需要使res减小,即i++使范围缩小,试图寻找符合要求的res
缩小
max
- 不变
- 变小
min
- 不变
- 变大
同理,res要么减小,要么不变
故如果res <= num(即符合题意),则arr[i + l…j - r]必符合要求
2.遇到的问题
- LinkedList方法不熟悉
- addLast()
- pollFirst()
- peekFirst()
- pollLast()
- peekLast()
3.代码
public class Solution {public int getNum(int[] arr, int num) {//临界条件if (arr == null || arr.length == 1) {return 0;}//两个队列分别存放数组下标LinkedList<Integer> min = new LinkedList<>();LinkedList<Integer> max = new LinkedList<>();int i = 0;int j = 0;int res = 0;//为什么用while不用for 因为i、j须用作两个循环的全局变量while (i < arr.length) {while (j < arr.length) {//符合压栈规则//min队列 大压小 不包括等于while (!min.isEmpty() && arr[j] <= arr[min.peekLast()]) {min.pollLast();}min.addLast(j);//符合压栈规则//max队列 小压大 不包括等于while (!max.isEmpty() && arr[j] >= arr[max.peekLast()]) {max.pollLast();}max.addLast(j);if (arr[max.peekFirst()] - arr[min.peekFirst()] > num) {break;}j++;}//下标是否过期 //i即将要自增 所以队头的值不能小于等于此时的iif (min.peekFirst() == i) {min.pollFirst();}if (max.peekFirst() == i) {max.pollFirst();}res += j - i; i++;}return res;}}
4.收获
学会寻找隐藏条件
- 如果某范围(不)符合条件,能否找出子范围或扩充范围必(不)符合条件
再依次巩固了窗口问题
栈和队列---算法题目相关推荐
- 数据结构——栈与队列相关题目
数据结构--栈与队列相关题目 232. 用栈实现队列 思路 225. 用队列实现栈 1.两个队列实现栈 2.一个队列实现栈 20. 有效的括号 思路 1047. 删除字符串中的所有相邻重复项 思路 1 ...
- 栈和队列--算法设计题3.15
假设以顺序存储结构实现一个双向栈,即在一维数组的存储空间中存在着两个栈,它们的栈底分别设在数组的两个端点.试编写实现这个双向栈tws的三个操作:初始化inistack(tws).入栈push(tws, ...
- 算法训练Day11 | LeetCode232. 用栈实现队列(模拟);225.用队列实现栈(模拟);20. 有效的括号(栈应用);1047. 删除字符串中的所有相邻重复项(栈应用)
目录 LeetCode232.用栈实现队列 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考 LeetCode225. 用队列实现栈 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考 ...
- 数据结构与算法——栈、队列、堆汇总整理
目录 例1:使用队列实现栈(easy 栈.队列) 例2:使用栈实现队列(easy 栈.队列) 例3:包含min函数的栈(easy 栈) 例4:合法的出栈序列(medium 栈.队列) 例5:简单的计算 ...
- 栈与队列2——用栈实现队列
用栈实现队列 题目说明 题目地址 题目说明 解题 方法一(使用两个栈 入队 - O ( n ) O ( n ) O(n)O(n) O(n)O(n), 出队 - O ( 1 ) O ( 1 ) O(1) ...
- CLRS10.1-6练习 - 用双栈实现队列
双栈实现队列算法: 分别考虑队列两种操作入队和出队,我们假设使用栈s1 s2, s1用来模拟入队,s2用来模拟出队 入队: 入队操作直接执行s1.push即可 出队: 代码实现 1 package h ...
- 【数据结构的魅力】002.单向、双向链表栈和队列递归
单向.双向链表 单链表和双链表最简单的练习 1)单链表和双链表如何反转 2)删除给定值的全部节点 遍历链表,找到第一个不是给定值的元素,即为头结点 while与if结合使用,通过两个变量(类似指针)来 ...
- LeetCode栈和队列练习
文章目录 前言 1.力扣20. 有效的括号 1.题目分析 2.代码示现 2.力扣225. 用队列实现栈 1.题目分析 2.代码实现 3.力扣232. 用栈实现队列 1.题目分析 2.代码实现 4.力扣 ...
- 用栈实现队列(图示超详解哦)
全文目录 引言 用栈实现队列 题目介绍 思路简述 实现 栈的部分 队列的部分 创建队列 判断队列是否为空 在队列尾入 在队列头出 访问队头元素 释放队列 总结 引言 在上一篇文章中,我们了解了用两个队 ...
最新文章
- 移动端html模板卡片列表效果,移动手机Material Design风格信息卡片UI设计
- InfluxDb中写入重复数据问题解决方案
- 函数式接口@FunctionalInterface使用示例
- U-Boot 图形化配置及其原理
- 华为Y9 Prime 2019曝光:无刘海无水滴全面屏+升降前摄
- Ubuntu 16.04 Steam
- java自学难点_学习JAVA遇到的难点总结
- LeetCode 410. Split Array Largest Sum
- 白岩松谈“房闹“现象
- MacOS Ventura 13.0 Beta8 (22A5352e) 带 OC 0.8.4 三分区原版黑苹果镜像
- 鄙视那些把爬虫当作AI的SB,清华学霸尹成大哥的历史上最强大的爬虫视频
- 使用微信企业号发送工资条教程
- 求解矩阵Ax=b最小二乘问题
- 爱我所爱,行我所行,听从我心,无问西东
- 浙江省二级c语言试题答案,浙江省计算机二级C语言上机题库
- 2020.11.04 使用OpenCV进行图像阈值分割操作【OpenCV C++】
- overleaf里出现红色log的原因
- 写了个静态的网页-模仿网易云
- Log4J的rootLogger的理解
- 水溶性彩铅品牌大点评
热门文章
- 程序员除了看技术类的书你们还看哪些书
- 9月29 Redis配置不当致使root被提权漏洞 | Found a swap file by the name swp
- 1(基于SpringBoot)疫情下的在线办公签到系统-从零构建后端项目基础篇
- 再见, 软交换!又一个通信时代的落幕
- 山东大学中外合作计算机专业,山东大学威海分校计算机专业
- 制作U盘启动盘重装macOS High Sierra
- 《BREW进阶与精通——3G移动增值业务的运营、定制与开发》连载之6---移动增值业务概述
- Nginx 理论+实例超详细讲解
- [python3.6]爬虫实战之爬取淘女郎图片
- 9.HTML基础——列表标签