常见算法题目:栈

395 阅读5分钟

前言

如果想进大厂工作,除了基础知识,项目经验,还有一些非常重要的能力需要锻炼,其中最重要的就是算法。算法题千千万,但是类型也就几十种,接下来的系列文章,我将面试中常见的算法题类型进行归纳。


栈面试题01. 三合一

三合一。描述如何只用一个数组来实现三个栈。

你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。
构造函数会传入一个stackSize参数,代表每个栈的大小。

1


分析:
二维数组
题目只要求我们使用「一个数组」来实现栈,并没有限制我们使用数组的维度。
因此一个简单的做法是,建立一个二维数组 datadata 来做。
二维数组的每一行代表一个栈,同时使用一个 locations,locations 记录每个栈「待插入」的下标。

具体步骤:
(1)获取二维数组的一行,即其中一个栈
(2)得到该栈待插入的下标
(3)进行相应的操作,push, pop等
(4)该栈待插入的下标+1,或者-1,根据情况变化


代码:

class TripleInOne {
    int N = 3;
    // 3 * n 的数组,每一行代表一个栈
    int[][] data; 
    // 记录每个栈「待插入」位置
    int[] locations; 

    public TripleInOne(int stackSize) {
        data = new int[N][stackSize];
        locations = new int[N];
    }
    
    public void push(int stackNum, int value) {
        int[] stk = data[stackNum];// 获取二维数组的一行,即一个栈
        int loc = locations[stackNum];// 查看该栈目前待插入的下标
        if (loc < stk.length) {
            stk[loc] = value;
            locations[stackNum]++;// 已经插入数据,该栈目前待插入的下标+1
        }
    }
    
    public int pop(int stackNum) {
        int[] stk = data[stackNum];
        int loc = locations[stackNum];
        if (loc > 0) {
            int val = stk[loc - 1];
            locations[stackNum]--;
            return val;
        } else {
            return -1;
        }
    }
    
    public int peek(int stackNum) {
        int[] stk = data[stackNum];
        int loc = locations[stackNum];
        if (loc > 0) {
            return stk[loc - 1];
        } else {
            return -1;
        }
    }
    
    public boolean isEmpty(int stackNum) {
        return locations[stackNum] == 0;
    }
}

栈面试题02. 堆盘子

堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。

进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。

当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.

image


分析:
放盘子push操作时,当最后一堆数量等于容量capacity时,就要新开一堆往里放。
拿盘子pop操作时,拿完之后如果这一堆没有盘子了,就得把这堆删除。popAt同理。
边界条件:即没有盘子时,pop 和 popAt都直接返回-1
隐藏边界条件:给定的初始化容量可能等于0,说明后边的操作都无法放置盘子。


代码:

class StackOfPlates {
    int capacity;// 每堆盘子的容量
    List<Deque<Integer>> allPlates; // 盘子堆

    // 初始化容量及盘子堆
    public StackOfPlates(int cap) {
        this.capacity = cap;
        allPlates = new LinkedList<>();
    }
    
    public void push(int val) {
        if(capacity <= 0){// 容量为0,不需要继续放盘子
            return;
        }
        // 没有盘子堆 或者 最后一堆的数量等于容量时,需要新开一堆盘子
        if(allPlates.isEmpty() || allPlates.get(allPlates.size() - 1).size() == capacity){
            allPlates.add(new ArrayDeque<>());
        }
        allPlates.get(allPlates.size()-1).offerFirst(val);// 放最后
    }
    
    public int pop() {
        // 没有盘子了 返回-1
        if(allPlates.size() == 0){
            return -1;
        }
        // 从最后一堆盘子顶上拿一个盘子
        int result = allPlates.get(allPlates.size() - 1).pollFirst();
        // 拿完之后,如果空了,就把这堆删掉
        if(allPlates.get(allPlates.size() - 1).size() == 0){
            allPlates.remove(allPlates.size() - 1);
        }
        return result; 
    }
    
    public int popAt(int index) {
        // 如果索引不合法,并且没有盘子了,就返回-1
        if(index < 0 || index >= allPlates.size() || allPlates.isEmpty()){
            return -1;
        }
        int result = allPlates.get(index).pollFirst();
        if(allPlates.get(index).size() == 0){
            allPlates.remove(index);
        }
        return result;
    }
}

栈面试题03. 化栈为队

实现一个MyQueue类,该类用两个栈来实现一个队列。

示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

说明: 你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。


代码:

class MyQueue {

    // stack的一些方法介绍
    // boolean empty() 测试堆栈是否为空
    // Object peek() 查看堆栈顶部的对象,但不从堆栈中移除它
    // Object pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象
    // Object push(Object element)把项压入堆栈顶部
    
    // 解题思路
    // (1)入队操作(push)时,先把数据存入stack1
    // (2)查询队列第一个数据(peek)时,把stack1的数据填充到stack2中,弹出Stack2的第
    // 一个数据(即结果),然后把stack2中的数据填充到stack1中
    // (3)删除队列第一个数据(pop)时,把stack1的数据填充到stack2中,弹出Stack2的第
    // 一个数据(即结果),然后把stack2中的数据填充到stack1中
    // (4)判断队列是否为空,stack1和stack2同时为空,则队列为空


    // 初始化两个栈stack1,stack2
    Stack<Integer> stack1;
    Stack<Integer> stack2;

    /** Initialize your data structure here. */
    // 在此初始化数据结构
    public MyQueue() {
        stack1 = new Stack<Integer>();
        stack2 = new Stack<Integer>();
    }
    
    /** Push element x to the back of queue. */
    // 将元素x推送到队列的后面
    public void push(int x) {
        stack1.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    // 从队列前面删除该元素并返回该元素
    public int pop() {
        while (!stack1.empty()) {
            stack2.push(stack1.pop());
        }

        int result = stack2.pop();

        while (!stack2.empty()) {
            stack1.push(stack2.pop());
        }

        return result;
    }
    
    /** Get the front element. */
    // 获取前元素
    public int peek() {
        while (!stack1.empty()) {
            stack2.push(stack1.pop());
        }

        int result = stack2.peek();

        while (!stack2.empty()) {
            stack1.push(stack2.pop());
        }

        return result;
    }
    
    /** Returns whether the queue is empty. */
    // 返回队列是否为空
    public boolean empty() {
        return stack1.empty() && stack2.empty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

栈面试题04. 栈排序

栈排序。编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。

image

说明:
栈中的元素数目在[0, 5000]范围内。


思路:
临时栈
题目要求只能使用一个临时栈存放数据
入栈时,先将栈中小于val的数值暂存到临时栈中
将val入栈
再将临时栈中的数据push会栈中


代码:

public class SortedStack {
  private Deque<Integer> stack, temp;

  public SortedStack() {
    stack = new LinkedList<>();
    temp = new LinkedList<>();
  }

  public void push(int val) {
    // 栈为空,入栈
    if (isEmpty()) stack.push(val);
    else {
      // 将栈中小于val的值暂存到temp中
      while (!stack.isEmpty() && val > stack.peek()) {
        temp.push(stack.pop());
      }
      // 入栈
      stack.push(val);
      // 将temp中暂存内容push回来
      while (!temp.isEmpty()) {
        stack.push(temp.pop());
      }
    }
  }

  public void pop() {
    if (stack.isEmpty()) return;
    stack.pop();
  }

  public int peek() {
    if (stack.isEmpty()) return -1;
    return stack.peek();
  }

  public boolean isEmpty() {
    return stack.isEmpty();
  }
}