[刷题丶数据结构]1.栈和队列
来源:互联网 发布:mac网络连接鉴定失败 编辑:程序博客网 时间:2024/04/25 09:17
前言
又开始学习新的一本书了,这本书《程序员代码面试指南:IT名企算法与数据结构题目最优解》 左程云写的。 讲述的是 一些常见的面试题的最优的解法,我粗略的看了下,我觉得这个不只是面试题。而真的觉得这些解题的思路很有用,能对基础知识有一个更深入的了解。
我也买了一本数据结构,相信大家也买过,无奈里面的各种公式啥的太专业了。看不太懂。还好这本书又是讲数据结构。还是用java来解题的。比较不错。我决定每天学习一道题。
一些基础知识
看了一些文章,说得都很专业。自己也不能太理解,简单的来说:
- 时间复杂度: 循环执行语句的次数(应该是时间长短?)
- 空间复杂度: 占用的空间大小
[前几天写得了,今天才整理笔记思路2015-11-07]
1. 设计一个有getMin功能的栈
题目:实现一个特殊的栈顶,在实现栈的pus基本功能的基础上,再实现返回栈中最小元素的操作,
要求:
- pop、push、getMin 操作时间复杂度都是O(1).
- 设计的栈类型可以使用现成的栈结构
难度:士★☆☆☆
思路:
其实最该题最主要的功能就在怎么来取得最小的值。
首先栈的数据结构是:先进后出。我们要统计栈中最小的值,很容易想到的一个办法就是 取出的时候排序。不过这样就特别的耗费时间,太low了。
书上给出的思路是:使用两个栈,一个栈存储正常的 数据。 一个栈用来存储每一步的最小的值。那么问题来了,怎么存储“每一步的最小的值呢?” 为什么要存储每一步最小的值呢?不能在压入的时候就一直拿着这个最小的值么? 那么就有这么一个问题。栈中的元素是可以压入弹出的,所以你得保证每一步的操作。你手中最小的值是 栈中存在的元素。 所以就得来计算“每一步的最小的值”
压入省空间,弹出费时间
/** * Created by zhuqiang on 2015/10/30 0030. */public class Client { public static void main(String[] args) { MyStack1 myStack = new MyStack1(); myStack.push(3); myStack.push(4); myStack.push(5); myStack.push(1); myStack.push(2); myStack.push(1); System.out.println(myStack.getMin()); myStack.pop(); System.out.println(myStack.getMin()); myStack.pop(); System.out.println(myStack.getMin()); myStack.pop(); System.out.println(myStack.getMin()); }}// 时间复杂度为o(1),空间复杂度为o(n)class MyStack1 { private Stack<Integer> stackData = new Stack<>(); private Stack<Integer> stackMin = new Stack<>(); /** 压入 **/ public void push(int newNum) { //压入省空间 if (stackMin.empty()) { // 首次压入的时候,由于都为空,所以把记录最小值的stack压入一个初始值 stackMin.push(newNum); }else if (newNum <= getMin()) { //如果再次压入的值,比 stackMin栈顶的值还要小,或则等于,就压入min中(为什么需要等于呢,是为了 弹出的时候,同步弹出min中与之相等的值) stackMin.push(newNum); } stackData.push(newNum); } /** 弹出 **/ public int pop() { //弹出废时间 if (stackData.empty()) { throw new RuntimeException("stack is empty"); } int value = stackData.pop(); if (value == getMin()) { // 如果弹出的值 与栈顶的值相等,则移除min中的栈顶值(push的时候判断了 小于等于,就是为了这里确保min中最小的值是stackData中存在的值) stackMin.pop(); } return value; } /** 获取最小值 **/ public int getMin() { if (stackMin.empty()) { throw new RuntimeException("stack is empty"); } return stackMin.peek(); }}
运行结果
1113
结果说明
- 依次压入:3、4、5、1、2、1。获取栈中最小值:应该是1。
- 弹出栈顶元素,应该弹出的是1,现在栈中元素有3、4、5、1、2。获取栈中最小值:应该还是1
- 弹出栈顶元素,应该弹出的是2,现在栈中元素有3、4、5、1。获取栈中最小值:应该还是1
- 弹出栈顶元素,应该弹出的是1,现在栈中元素有3、4、5。获取栈中最小值:应该是3
上面的步骤,和要求的一致,达到了效果。
压入费空间,弹出省时间
// 压入费空间,弹出省时间class MyStack2 { private Stack<Integer> stackData = new Stack<>(); private Stack<Integer> stackMin = new Stack<>(); /** 压入 **/ public void push(int newNum) { //压入废时间 if (stackMin.empty()) { stackMin.push(newNum); }else if (newNum <= getMin()) { stackMin.push(newNum); }else{ stackMin.push(getMin()); //如果是大于的话,就把当前最小的重复压入, } stackData.push(newNum); } /** 弹出 **/ public int pop() { //弹出废时间 if (stackData.empty()) { throw new RuntimeException("stack is empty"); } stackMin.pop(); //直接弹出,因为在push的时候同步了对应的每一个操作。所以不用判断 return stackData.pop(); } /** 获取最小值 **/ public int getMin() { if (stackMin.empty()) { throw new RuntimeException("stack is empty"); } return stackMin.peek(); } }
点评
上面两种实现方式,大体都差不多,区别在于,压入和弹出有区别。
- 第一次压入数据的时候,由于minStack中没有数据,就作为初始值同步压入到两个栈中
- 第二次压入数据的时候,会和minStack中栈顶的值进行判断,如果小于就把这个数压入minStack中
- 在移除的时候,需要同步的判断 移除的值是否和minStack栈顶的值是否相等。是 就同步移除。这样才能保证,你手中最小的元素 是栈中存在的值。
上面是 压入省空间,弹出费时间 的实现思路,对于这个“压入省空间,弹出费时间”的定义,得有比较,然后对比来看:
- 压入省空间:压入minStack的时候,示例1,只判断了小于的最小值时候才压入,而示例2中是每一步都压入了元素
- 弹出费时间:弹出Stack的时候,因为要同步的操作minStack,示例1,要判断是否等于当前弹出的值。 而示例2:不需要判断,直接弹出。
[2015-11-08]
由两个栈组成的队列
题目:编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)
难度:尉★★☆☆☆
思路:
首先栈的特点是,先进后出,而队列的特点是,先进先出,两个栈,正好把顺序颠倒过来。
难点:颠倒的时机是什么?存储的话怎么存储?取出后又怎么操作,才能保证取出后又压入了数据,再取出的时候顺序是正确的。 这个是我想自己实现这个要求的时候想到的问题。然后就是为了这个顺序。我就彻底没有招了。看了书上的解答才想明白。看下面的图:(把图片拖动在浏览器另外的窗口打开就能看见大图了
)
- 我们依次压入5、4、3
- 当要弹出的时候,会先判断
压入栈
中是否为空,为空的时候,才把压入栈
中的数据弹出并压入弹出栈
中(压入顺序3、4、5)。刚好倒过来了。 - 从
弹出栈
弹出数据,弹出了5. - 如果,这个时候 我再压入一个数字2,那么此时的
压入栈
中,其实已经没有数据了。而弹出栈
中还有4、3的元素。(压入栈
存在的元素:2) - 如果,这个时候弹出一个元素,
弹出栈
中有数据,所以直接弹出了4.
其实到这里,应该就明白了。队列的特点是 先进先出。而栈的特点的先进后出。 最容易迷糊的地方其实就是,压入数据后,弹出数据,再压入好像保存弹出的数据挺困难的。 其实就是这里。 看了上面的分析之后。 因为 5被取走了。剩下的就是4、3,而这两个数据是最早进来的。和后面压入的数据没有半毛钱关系。所以都不需要去理会顺序的问题。要把图上的两个栈看成一个整体。
也就是说要做到顺序不乱,就得保证以下两点,就不会出错了:
- 如果
压入栈
要往弹出栈
压入数据,要一次性把压入栈
的数据弹出完。 - 如果
弹出栈
不为空,是不能往弹出栈压入数据的。
/** * @author zhuqiang * @version V1.0 * @date 2015/11/8 21:43 */public class Client { public static void main(String[] args) { TwoStacksQueue tsq = new TwoStacksQueue(); tsq.add(5); tsq.add(4); tsq.add(3); System.out.println(String.format("预期弹出:%s,实际弹出:%s",5,tsq.poll())); System.out.println(String.format("预期弹出:%s,实际弹出:%s",4,tsq.peek())); tsq.add(2); System.out.println(String.format("预期弹出:%s,实际弹出:%s",4,tsq.peek())); }}class TwoStacksQueue{ private Stack<Integer> pushStack = new Stack<>(); //压入栈 private Stack<Integer> popStack = new Stack<>(); //弹出栈 public void add(int pushInt){ pushStack.push(pushInt); } // 弹出栈顶元素,并移除 public int poll(){ if(pushStack.empty() && popStack.empty()){ throw new RuntimeException("Queue is empty!"); }else if(popStack.empty()){ //弹出栈为空的时候,才把压入栈的元素往弹出栈中取出并压入到弹出栈中 while(!pushStack.empty()){ popStack.push(pushStack.pop()); } } return popStack.pop(); } //弹出栈顶元素,不移除 public int peek(){ if(pushStack.empty() && popStack.empty()){ throw new RuntimeException("Queue is empty!"); }else if(popStack.empty()){ //弹出栈为空的时候,才把压入栈的元素往弹出栈中取出并压入到弹出栈中 while(!pushStack.empty()){ popStack.push(pushStack.pop()); } } return popStack.peek(); }}
运行结果
预期弹出:5,实际弹出:5预期弹出:4,实际弹出:4预期弹出:4,实际弹出:4
[2015-11-09]
如何只用递归函数和栈操作逆序一个栈
题目:一个栈依次压入1、2、3、4、5,那么栈顶到栈底分别为:5、4、3、2、1。将这个栈转置后,从栈顶到栈底为:1、2、3、4、5,也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。
难度:尉★★☆☆☆
思路:
- 只能用站statck操作和递归函数,不能用其他的操作。
- 那么就只能想法把栈中的数据全部弹出,然后再压入。
- 取出的时候,顺序是从栈底取出(递归的取出 放到内存中)
- 再次压入栈中的时候,从内存中递归的压入回去。
上面的思路。光看这几点,肯定是迷糊的。先看一个总体的思路图。再看具体的递归函数运行流程图。
(把图片拖动在浏览器另外的窗口打开就能看见大图了
)
- 入口函数是从
递归函数二
开始 - 在
函数二
中,调用函数一
,获得栈底的值。然后再递归调用获取栈顶底的值。结束递归的条件是:stack中被取空了 - 在
函数二
中递归结束的时候,逐级往上返回的时候,首先要明白的是,在递归结束的时候,我们拿到的值是:3.然后在这个时机压入被取空的stack中。 就造成了上图中的流程。
/** * @author zhuqiang * @version V1.0 * @date 2015/11/9 22:23 */public class Client { public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); reverse(stack); System.out.println("*********** 打印反转之后栈中的顺序"); while (!stack.empty()){ System.out.println(stack.pop()); } } // 获取并移除栈底的值 public static int getAndRemoveLastElement(Stack<Integer> stack){ Integer result = stack.pop(); if(stack.empty()){ //结束条件,栈为空 return result; }else{ int last = getAndRemoveLastElement(stack); stack.push(result); //递归结束的时候,除了栈底的元素都会被再次重新被压入statck中 return last; //返回了 栈底的值 } } public static void reverse(Stack<Integer> stack){ if(stack.empty()){ return; } int i = getAndRemoveLastElement(stack); //获取一个栈底的值,暂时不做处理(就相当于放在了方法内存中) reverse(stack); //递归的获取栈底的值 stack.push(i); //在这个递归中,最后一层拿到的值,肯定是之前栈顶的值(也就是说,这里压入的顺序其实就相当于:把之前的栈循环的弹出,然后又压入了另外一个栈) }}
运行结果
*********** 打印反转之后栈中的顺序12345
可以看到,最先取出的是1,要知道栈的特性是:先进后出。之前栈先出来的应该是最后一个被压入的5,所以这个结果是符合题目要求的。
贴上书上的两张图,画的太好了。理解下两个递归函数的处理流程。
getAndRemoveLastElement 获取并移除栈底的值
reverse 反转
[2015-11-25]
猫狗队列
题目:
宠物、猫、狗类如下:
public class Pet { private String type; //类型 public Pet(String type) { this.type = type; } public String getType() { return type; }}public class Cat extends Pet{ public Cat() { super("cat"); }}public class Dog extends Pet { public Dog() { super("dog"); }}
实现一种猫狗队列的结构,要求如下:
- 用户可以调用add方法将cat活dog类的实例放入队列中
- 用户可以调用pollAll方法,将队列中所有的实例按照先进先出的原则弹出。
- 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的顺序弹出
- 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的顺序弹出
- 用户可以调用isEmpty方法,判断队列是否为空
- 用户可以调用isDogEmpty方法,判断队列中的dog是否为空
- 用户可以调用iseCatEmpty方法,判断队列中的cat是否为空
难度:士★☆☆☆☆
思路:
看题目中,有区别对待的,可以使用两个队列来分别存放,要解决的问题就是:先后顺序怎么判定
好了,还有其他的要求,说不能修改给出的类的结构。我们要判断先后顺序,也就是要在取出的时候判断dog先放进去还是cat先放进去,那么就可以使用 一个计数器来解决。
总结思路:使用创建自定义队列,分别用两个队列来存放dog和cat,但是进入队列的不是pet类,而是我们自己包装的pet类,里面有一个计数器。用来判定先后顺序。
/** * 进入我们的队列的,实体包装类 * @author zhuqiang * @version V1.0 * @date 2015/11/22 20:55 */public class PetEnter { private long count; private Pet pet; public PetEnter(Pet pet, long count) { this.pet = pet; this.count = count; } public long getCount() { return count; } public Pet getPet() { return pet; } public String getType(){ return pet.getType(); }}/** * @author zhuqiang * @version V1.0 * @date 2015/11/22 20:58 */public class DogCatQueue { private Queue<PetEnter> dog = new LinkedList<>(); private Queue<PetEnter> cat = new LinkedList<>(); private long count = 0; public void add(Pet pet){ if(pet == null){ throw new IllegalArgumentException("不能插入空元素"); }else if("dog".equals(pet.getType())){ dog.add(new PetEnter(pet,count++)); }else if("cat".equals(pet.getType())){ cat.add(new PetEnter(pet,count++)); }else { throw new IllegalArgumentException("不支持的类型"); } } //依次弹出所有的元素:看题目我也不知道弹出all是什么意思,看了实现才知道,是在猫和狗的队列中按照先进显出的顺序弹出一个, public Pet pollAll(){ if(!dog.isEmpty() && !cat.isEmpty()){ if(dog.peek().getCount() > cat.peek().getCount()){ return cat.poll().getPet(); }else{ return dog.poll().getPet(); } }else if(!dog.isEmpty()){ return dog.poll().getPet(); }else if(!cat.isEmpty()){ return cat.poll().getPet(); }else { throw new RuntimeException("队列为空"); } } public Pet pollDog(){ if(!dog.isEmpty()) { return dog.poll().getPet(); }else { throw new RuntimeException("队列为空"); } } public Pet pollCat(){ if(!cat.isEmpty()) { return cat.poll().getPet(); }else { throw new RuntimeException("队列为空"); } } //检测队列是否为空 public boolean isEmpty(){ return dog.isEmpty() && cat.isEmpty(); } public boolean isDogEmpty(){ return dog.isEmpty(); } public boolean isCatEmpty(){ return cat.isEmpty(); }}public class Client { public static void main(String[] args) { DogCatQueue dq = new DogCatQueue(); dq.add(new Dog()); dq.add(new Dog()); System.out.println(String.format("队列是否为空:%s",dq.isEmpty())); dq.add(new Cat()); System.out.println(String.format("取出一个dog=%s",dq.pollDog().getType())); dq.add(new Cat()); System.out.println(String.format("pollAll,期望弹出dog,实际弹出:%s",dq.pollAll().getType())); System.out.println(String.format("isDogEmpty,期望为true,实际=%s",dq.isDogEmpty())); System.out.println(String.format("pollCat,期望为cat,实际=%s",dq.pollCat().getType())); }}
运行结果
队列是否为空:false取出一个dog=dogpollAll,期望弹出dog,实际弹出:dogisDogEmpty,期望为true,实际=truepollCat,期望为cat,实际=cat
[2015-11-24]
用一个栈实现另一个栈的排序
题目:一个栈中元素为整型,现在想将该栈从栈顶到底按大到小的顺序排序,指许申请一个栈,除此之外,可以申请新的变量,但是不能申请其他的数据结构。
难度:士★☆☆☆☆
我看到这个题的时候,想的是:不能用其他的数据结构,只能在内存中交换,用如何只用递归函数和栈操作逆序一个栈
这个来做,没办法没有任何思路。
思路:
将排序的栈标记为statck,辅助栈为help,在stack上执行pop操作,弹出的元素比标记为cur。
- 如果cur小于或等于help栈顶的元素,则将cur直接压入 help;
- 如果cur大于help栈顶的元素,则将help的元素逐个弹出并压入statc中,再将cur压入help中。
经过上面两个步骤的操作,其实就是说,只要有最大的元素出现,那么help中的元素都将被弹出(并且是按照小到大的顺序弹出),然后压回stack中,也就是说,help中栈底的元素始终是最大的,栈顶始终是最小的。只能这样,在排序完成之后,压回stack的时候,是按照从从小到大的顺序压入,那么栈顶到栈底就是按照大到小的顺序排列的,取出来的时候就是降序排列。
难就难在,栈的先进后出的顺序,被颠倒了好几次。但是最终的目的是,help中是栈顶到底的顺序是:从小到大。
/** * @author zhuqiang * @version V1.0 * @date 2015/11/24 21:28 */public class Client { public static void main(String[] args) { Stack<Integer> stack= new Stack(); stack.push(1); stack.push(5); stack.push(3); stack.push(2); Stack<Integer> help = new Stack(); while (!stack.isEmpty()){ int cur = stack.pop(); while (!help.isEmpty() && help.peek() <= cur){ //升序 <= 将序。最后的结果 stack.push(help.pop()); } help.push(cur); } while (!help.isEmpty()){ stack.push(help.pop()); } }}
- [刷题丶数据结构]1.栈和队列
- 数据结构-栈和队列
- 数据结构-栈和队列
- 数据结构--栈和队列
- 数据结构-栈和队列
- 数据结构-栈和队列
- 数据结构 栈和队列
- 数据结构-栈和队列
- 数据结构-栈和队列
- 数据结构-栈和队列
- 数据结构:栈和队列
- 数据结构-栈和队列
- 数据结构--栈和队列
- 【数据结构】队列和栈
- 数据结构--栈和队列
- 【数据结构】-栈和队列
- 数据结构 栈和队列
- 数据结构-栈和队列
- 粒子群算法求解优化问题(c实现)
- httpwebrequest详解【转】
- 内核pppoe接收发送数据包流程
- 进程间的通信机制
- 看不懂
- [刷题丶数据结构]1.栈和队列
- XMPP框架 微信项目开发之登录的实现——登录的步骤示例
- oracle备份
- Android TouchEvent事件传递机制
- leetcode-Remove Duplicates from Sorted List
- 在eclipse上配置copy来的web项目
- hdoj Matrix multiplication 4920 (矩阵相乘)
- HDOJ 2531 Catch him 【 BFS 】
- 行为型模式之九——模板方法模式