[刷题丶数据结构]1.栈和队列

前言

  又开始学习新的一本书了,这本书《程序员代码面试指南:IT名企算法与数据结构题目最优解》 左程云写的。 讲述的是 一些常见的面试题的最优的解法,我粗略的看了下,我觉得这个不只是面试题。而真的觉得这些解题的思路很有用,能对基础知识有一个更深入的了解。
  我也买了一本数据结构,相信大家也买过,无奈里面的各种公式啥的太专业了。看不太懂。还好这本书又是讲数据结构。还是用java来解题的。比较不错。我决定每天学习一道题。

一些基础知识

看了一些文章,说得都很专业。自己也不能太理解,简单的来说:

  1. 时间复杂度: 循环执行语句的次数(应该是时间长短?)
  2. 空间复杂度: 占用的空间大小

[前几天写得了,今天才整理笔记思路2015-11-07]

1. 设计一个有getMin功能的栈

题目:实现一个特殊的栈顶,在实现栈的pus基本功能的基础上,再实现返回栈中最小元素的操作,
要求:

  1. pop、push、getMin 操作时间复杂度都是O(1).
  2. 设计的栈类型可以使用现成的栈结构

难度:士★☆☆☆

思路:
  其实最该题最主要的功能就在怎么来取得最小的值。
  首先栈的数据结构是:先进后出。我们要统计栈中最小的值,很容易想到的一个办法就是 取出的时候排序。不过这样就特别的耗费时间,太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();
    }
}

运行结果

1
1
1
3

结果说明

  1. 依次压入:3、4、5、1、2、1。获取栈中最小值:应该是1。
  2. 弹出栈顶元素,应该弹出的是1,现在栈中元素有3、4、5、1、2。获取栈中最小值:应该还是1
  3. 弹出栈顶元素,应该弹出的是2,现在栈中元素有3、4、5、1。获取栈中最小值:应该还是1
  4. 弹出栈顶元素,应该弹出的是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();
    }
 }   

点评

上面两种实现方式,大体都差不多,区别在于,压入和弹出有区别。

  1. 第一次压入数据的时候,由于minStack中没有数据,就作为初始值同步压入到两个栈中
  2. 第二次压入数据的时候,会和minStack中栈顶的值进行判断,如果小于就把这个数压入minStack中
  3. 在移除的时候,需要同步的判断 移除的值是否和minStack栈顶的值是否相等。是 就同步移除。这样才能保证,你手中最小的元素 是栈中存在的值。

上面是 压入省空间,弹出费时间 的实现思路,对于这个“压入省空间,弹出费时间”的定义,得有比较,然后对比来看:

  1. 压入省空间:压入minStack的时候,示例1,只判断了小于的最小值时候才压入,而示例2中是每一步都压入了元素
  2. 弹出费时间:弹出Stack的时候,因为要同步的操作minStack,示例1,要判断是否等于当前弹出的值。 而示例2:不需要判断,直接弹出。

[2015-11-08]

由两个栈组成的队列

题目:编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)

难度:尉★★☆☆☆

思路:

首先栈的特点是,先进后出,而队列的特点是,先进先出,两个栈,正好把顺序颠倒过来。

难点:颠倒的时机是什么?存储的话怎么存储?取出后又怎么操作,才能保证取出后又压入了数据,再取出的时候顺序是正确的。 这个是我想自己实现这个要求的时候想到的问题。然后就是为了这个顺序。我就彻底没有招了。看了书上的解答才想明白。看下面的图:(把图片拖动在浏览器另外的窗口打开就能看见大图了
这里写图片描述

  1. 我们依次压入5、4、3
  2. 当要弹出的时候,会先判断 压入栈 中是否为空,为空的时候,才把 压入栈 中的数据弹出并压入 弹出栈 中(压入顺序3、4、5)。刚好倒过来了。
  3. 弹出栈弹出数据,弹出了5.
  4. 如果,这个时候 我再压入一个数字2,那么此时的压入栈中,其实已经没有数据了。而弹出栈中还有4、3的元素。(压入栈存在的元素:2)
  5. 如果,这个时候弹出一个元素,弹出栈中有数据,所以直接弹出了4.

其实到这里,应该就明白了。队列的特点是 先进先出。而栈的特点的先进后出。 最容易迷糊的地方其实就是,压入数据后,弹出数据,再压入好像保存弹出的数据挺困难的。 其实就是这里。 看了上面的分析之后。 因为 5被取走了。剩下的就是4、3,而这两个数据是最早进来的。和后面压入的数据没有半毛钱关系。所以都不需要去理会顺序的问题。要把图上的两个栈看成一个整体。

也就是说要做到顺序不乱,就得保证以下两点,就不会出错了:

  1. 如果压入栈要往弹出栈压入数据,要一次性把压入栈的数据弹出完。
  2. 如果弹出栈不为空,是不能往弹出栈压入数据的。
/**
 * @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,也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。

难度:尉★★☆☆☆

思路:

  1. 只能用站statck操作和递归函数,不能用其他的操作。
  2. 那么就只能想法把栈中的数据全部弹出,然后再压入。
  3. 取出的时候,顺序是从栈底取出(递归的取出 放到内存中)
  4. 再次压入栈中的时候,从内存中递归的压入回去。

上面的思路。光看这几点,肯定是迷糊的。先看一个总体的思路图。再看具体的递归函数运行流程图。
这里写图片描述把图片拖动在浏览器另外的窗口打开就能看见大图了

  1. 入口函数是从递归函数二开始
  2. 函数二中,调用函数一,获得栈底的值。然后再递归调用获取栈顶底的值。结束递归的条件是:stack中被取空了
  3. 函数二中递归结束的时候,逐级往上返回的时候,首先要明白的是,在递归结束的时候,我们拿到的值是: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); //在这个递归中,最后一层拿到的值,肯定是之前栈顶的值(也就是说,这里压入的顺序其实就相当于:把之前的栈循环的弹出,然后又压入了另外一个栈)
    }
}

运行结果

***********  打印反转之后栈中的顺序
1
2
3
4
5

可以看到,最先取出的是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");
    }
}

实现一种猫狗队列的结构,要求如下:

  1. 用户可以调用add方法将cat活dog类的实例放入队列中
  2. 用户可以调用pollAll方法,将队列中所有的实例按照先进先出的原则弹出。
  3. 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的顺序弹出
  4. 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的顺序弹出
  5. 用户可以调用isEmpty方法,判断队列是否为空
  6. 用户可以调用isDogEmpty方法,判断队列中的dog是否为空
  7. 用户可以调用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=dog
pollAll,期望弹出dog,实际弹出:dog
isDogEmpty,期望为true,实际=true
pollCat,期望为cat,实际=cat

[2015-11-24]

用一个栈实现另一个栈的排序

题目:一个栈中元素为整型,现在想将该栈从栈顶到底按大到小的顺序排序,指许申请一个栈,除此之外,可以申请新的变量,但是不能申请其他的数据结构。

难度:士★☆☆☆☆

我看到这个题的时候,想的是:不能用其他的数据结构,只能在内存中交换,用如何只用递归函数和栈操作逆序一个栈这个来做,没办法没有任何思路。

思路:
将排序的栈标记为statck,辅助栈为help,在stack上执行pop操作,弹出的元素比标记为cur。

  1. 如果cur小于或等于help栈顶的元素,则将cur直接压入 help;
  2. 如果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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值