stack & queue及经典例题

1. 用2个stack sort numbers

给定一个stack1,里面的数是unsorted,只允许用额外一个stack2去sort这个stack。

分析思路如下:

  • 一个global_min来放当前最小值(初始为stack1.pop()出来的值)
  • 然后依次和stack1.pop()出来的值进行比较,如global_min<stack1.pop(),则把stack1.pop()的值压入stack2,继续比较;如global_min>stack1.pop(),则把global_min压入stack2,global_min换成stack.pop()
  • 直到比完stack1最后一个值,再把第一轮最后的global_min压入stack1,此时的global_min为最小值
  • 把stack2中的数再压回stack1
  • 接着比下一轮时要注意,不是从stack1最底层开始比,要从stack1未sort好的部分开始比,即相当于每次压入global_min就在其后加一个挡板,之后的每一轮都从它之后的unsorted的数比较
  • 一直重复,直到『挡板』移到stack1最后一个数

图示:

 

* 如果stack1里有很多重复的元素,有没有更优化的方案?

 思路1:我们可以把和global_min重复的元素也放入stack2

 思路2:使用counter,记录和global_min重复的元素,但是并不压入stack2,最后global_min压入stack1的时候,根据counter,压入等量的数

我们选择思路2:

 

代码如下:

注意这里的stack是用LinkedList(参考LinkedList)实现的,所以这里的

peekFirst==peek

offerFirst==push

pollFirst==pop

可以看成是deque,但是只用一边进出

public class Solution {
  public void sort(LinkedList<Integer> s1) {
    LinkedList<Integer> s2 = new LinkedList<Integer>();
    // Write your solution here.
    int n=s1.size();
    sort_stack(s1,s2,n);
  }
  private void sort_stack(LinkedList<Integer> s1, LinkedList<Integer> s2, int size){
    int i=0;
    
    while(i<size-1){
      Integer global_max=s1.pollFirst();
      int counter=1;
          for(int j=0;j<size-i-1;j++){
              if(s1.peekFirst()<global_max){
                  s2.offerFirst(s1.pollFirst());
              }else if(s1.peekFirst()==global_max){
                  s1.pollFirst();
                  counter+=1; 
              }else{
                  for(int k=1; k<=counter;k++){
                      s2.offerFirst(global_max);
                    } 
                  global_max=s1.pollFirst();
                  counter=1;
              }   
          }
            for(int k=1; k<=counter;k++){
                s1.offerFirst(global_max);
            } 
          while(!s2.isEmpty()){
              s1.offerFirst(s2.pollFirst());
          }        
          i++;
      }
     }
}

 2. 用两个stack实现一个queue

enqueue: (in)stack1: 用来存新的元素

dequeue: (out)stack2:用来pop:case1 如果stack2非空,直接pop —— O(1)

             case2 如果stack2为空,先把所有的元素从stack1倒到stack2,再pop —— O(n)

我们来分析一下armotized time complexity of dequeue:

  • 1st: n*stack1.pop()+n*s2.push()+1
  • 2nd: 1
  • 3rd: 1
  • .
  • .

所以:(2n+1+1*(n-1))/n=3=O(1)

(分子是underlying stack operations,分母是dequeue operations that we can support)

代码如下:

public class Solution {
    private Deque<Integer> stack1=new LinkedList<>();
    private Deque<Integer> stack2=new LinkedList<>();
  private void shuffle(){
    while(!stack1.isEmpty()){
      stack2.push(stack1.pop());
    }
  }
  public Integer poll() {
    if(stack2.isEmpty()){
      shuffle();
    }
    if(stack2.isEmpty()){
        return null;
     }
    return stack2.pop();
  }
  
  public void offer(int element) {
    stack1.push(element);
  }
  
  public Integer peek() {
   if(stack2.isEmpty()){
            shuffle();
    }
    if(stack2.isEmpty()){
          return null; 
     }
    return stack2.peek();
  }
  
  public int size() {
    return stack1.size()+stack2.size();
  }
  
  public boolean isEmpty() {
    if(stack2.isEmpty() && stack1.isEmpty()){
            return true;
    }
    return false;
  }
}
View Code

 

*变种题1:两个queue实现一个stack

Q:->xxxxxx->

S:  ->xxxxxx

    <-

push(): 直接往Q1放

pop(): 把除了最后的元素都移到Q2,然后再Q1.pop()

然后直接换一下reference,下次就又可以直接操作Q1

此时Q2的作用相当于一个buffer

*变种题2:一个queue实现一个stack

此时我们要假设可以一直知道queue的size

push():直接从q push

pop(): 此时需要先把顶层前面的元素pop出来,然后从屁股push回去,直到把顶层元素压到最底层,pop出来

 3. 用stack实现min() function

pop() push() top() min()

stack1:is used to store input elements

minstack:is used to store the min element so far in s1 (as its top elements)

TC=O(1)

SC=O(n)

要注意的是,minstake里的数要和stack1的同步,比如stack1pop出一个数,如果这个数也再minstack的顶上,我们也要pop出

public class Solution {
  public Solution() {
    // write your solution here
  }
  Deque<Integer> stack=new LinkedList<>();
  Deque<Integer> minstack=new LinkedList<>();
  public int pop() {
    if(stack.isEmpty()){
        return -1;
    }
    Integer element=stack.pop();
    if(element.equals(minstack.peek())){
          minstack.pop();
    }
    return element;
  }
  
  public void push(int element) {
    stack.push(element);
    if(minstack.isEmpty()||element<=minstack.peek()){
      minstack.push(element);
    }
  }
  
  public int top() {
    if(stack.isEmpty()){
        return -1;      
    }
        return stack.peek();
  }
  
  public int min() {
    if(stack.isEmpty() && minstack.isEmpty()){
     return -1; 
    }
    return minstack.peek();
  }
}
View Code

 

4. 用2或3个stack实现deque

deque:left.add() left.remove() right.add() right.remove()

1)用两个stack:头对头

->     ||s1 s2||    ->

<-                  <-

left.add()=s1.push()——O(1)

right.add()=s2.push()——O(1)

left.remove():如果S1非空,则直接S1.pop()——O(1)

       如果S1为空,则把所有元素从S2移到S1,再S1.pop() ——O(n)

同理,right.remove()

那我们来算一下remove的amortized time

worst case应该是 

L.remove(); ——2n+1

R.remove(); ——2(n-1)+1

L.remove(); ——2(n-2)+!

R.remove(); ——2(n-3)+!

……

amotized time=(2n+!+2(n-1)+1+……+1)/n=n

无法amortize成O(1)

2)三个strck来提高 remove的TC

S1和S2仍然作为左右手,而S3则作为一个buffer,来存放一半的元素

例如deque=5678

56||S1 S2||78

如果,初始状态为:

||S1 S2||5678

我们希望最后变成:

56||S1 S2||78

  • 先把S2的一半7,8pop 出S2,压入S3:n/2 pop out of S2, n/2 push into S3
  • 再把剩的一半pop出S2,压入S1:n/2 pop out of S2, n/2 push into S1
  • 再把7,8压回S2:n/2 pop out of S3, n/2 push into S2

n/2*6=3n。之后的n/2个操作,我们只需要pop对应边的元素就行了

此时的amortized time=(3n+1*n/2)/(n/2)=7=O(1)

 

总结:

1. 什么时候需要往stack上考虑?

当需要从左到右scan一个array/string时,如果要不断的回头看左边最新的元素师

eg:逆波兰表达式:a*(b+c) -> abc+*

public class Solution {
  public int evalRPN(String[] tokens) {
    // Write your solution here
    Deque<Integer> stack=new LinkedList<>();
        for(int i=0;i<tokens.length;i++){
         if(tokens[i]!="+" && tokens[i]!="-" && tokens[i]!="*" && tokens[i]!="/"){
              Integer op = Integer.valueOf(Integer.parseInt(tokens[i]));  
                stack.push(op);
          }else{
                   int op1=stack.pop(); 
                int op2=stack.pop();
                int res=0;
                if(tokens[i]=="+"){
                    res=op1+op2;
              }else if(tokens[i]=="-"){
                    res=op2-op1; //顺序
              }else if(tokens[i]=="*"){
                 res=op2*op1; 
              }else{
                    res=op2/op1; 
              }
            stack.push(res);
          }
        }
        return stack.pop();
      }
}

 

2. 常见性质:S1的所有元素倒进S2, S2的顺序完全reverse

S1->S2->S1顺序不变,amortized tc分摊到每一个move的元素时间为O(1)

posted @ 2018-03-19 01:26  机智的小八  阅读(643)  评论(0编辑  收藏  举报