代码随想录——栈与队列

(一)栈和队列基础

SGI STL中,默认以deque为缺省情况下栈和队列的底层结构

deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑

可以指定vector为栈的底层实现,初始化语句

std::stack<int, std::vector<int>> third; 

也可以指定list为队列底层实现,初始化语句

std::queue<int, std::list<int>> third;

(二)用栈实现队列

使用两个栈,一边出栈,一边入栈

这样改变元素的顺序,将原来在栈底的元素出栈,模拟出队列的行为

注意需要出栈的时候要把所有元素都先在另一个栈里入栈,保证顺序不改变

stackIn; stackOut;
void push(int x){
  stackIn.push(x);
}
int pop(){
  if(stackOut.empty(){
    while(!stackIn.empty()){
      stackOut.push(stackIn.top());
      stackIn.pop();
    }
    result = stackOut.top();
    stackOut.pop();
    return result;
  }
int peek(){
  result = this -> pop();
  //只查询,不弹出,所以调用后还需要放回
  stackOut.push(result);
  return result;
}
class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;

    MyQueue() {

    }
    
    void push(int x) {
        stIn.push(x);
    }
    
    int pop() {
        if(stOut.empty()){
            while(!stIn.empty()){
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;
    }
    
    int peek() {
        int result = pop();
        stOut.push(result);
        return result;
    }
    
    bool empty() {
        return stIn.empty() && stOut.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();
 * bool param_4 = obj->empty();
 */

(三)用队列实现栈

用一个队列模拟栈

将需要弹出的元素前面的元素(size - 1)先都取出来再放进队列,直到最后一个元素变成第一个元素。

que
void push(int x){
  que.push(x);
}
int pop(){
  size = que.size();
  size --;
  //弹出size - 1 个元素
  while(size --){
    que.push(que.front());
    que.pop();
  }
  int result = que.front();
  que.pop();
  return result;
}
int top(){
  //栈顶即队尾
  return que.back();
}
class MyStack {
public:
    queue<int> que;
    MyStack() {

    }
    
    void push(int x) {
        que.push(x);
    }
    
    int pop() {
        int size = que.size();
        size --;
        while(size --){
            que.push(que.front());
            que.pop();
        }
        int result = que.front();
        que.pop();
        return result;
    }
    
    int top() {
        return que.back();
    }
    
    bool empty() {
        return que.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

(四)有效的括号

三种不匹配情况

① ( [ { } ] ( ) -----左括号多余 (字符串遍历完,栈不为空)

② [ { ( } ] -----括号不匹配 (和栈顶元素匹配不成功)

③ [ { } ] ( ) ) ) ) -----右括号多余 (字符串还没遍历完,栈为空了)

方法:

遇到 [ → ] 入栈

遇到 { → } 入栈

遇到 ( → )入栈

好处:如果将左括号入栈,那么出栈时,还需要有一次左括号和右括号的对应匹配

这样做只要判断入栈的元素和栈顶元素是否相同就可以了

stack<char> st;
//剪枝,如果为s长度奇数,则一定不匹配
if(s.size()%2 !=0) return false;
for(int i = 0; i < s.size(); i++){
  if(s[i] == '(') st.push(')');
  else if(s[i] == '[') st.push(']');
  else if(s[i] == '{') st.push('}');
  //先判定栈是否为空,否则st.top可能报错
  // st.empty()针对第③种情况,st.top != s[i]针对第②种情况
  else if(st.empty() || st.top() != s[i])
    return false;
  //匹配情况,消除栈顶元素
  else st.pop();
}
//对应第①种异常,如果s遍历完栈不为空,返回false,不为空,则匹配,返回true
return st.empty();
}
class Solution {
public:
    bool isValid(string s) {
        stack<int> st;
        if(s.size() % 2 != 0){
            return false;
        }
        for(int i = 0; i < s.size(); i++){
            if(s[i] == '(') st.push(')');
            else if(s[i] == '[') st.push(']');
            else if(s[i] == '{') st.push('}');
            else if(st.empty() || st.top() != s[i]) return false;
            else st.pop();
        }
        return st.empty();
    }
};

(五)删除字符串中的所有相邻重复项

匹配问题:栈

方法一:建立一个新栈,类比括号匹配问题,但由于栈弹出的元素是倒叙的,需要最后对字符串反转。

class Solution {
public:
    string removeDuplicates(string s) {
        stack<int> st;
        for(int i = 0; i< s.size(); i++){
            if(!st.empty() && s[i] == st.top()){
                st.pop();
            }else{
                st.push(s[i]);
            }
        }
        string result = "";
        while(!st.empty()){
            result += st.top();
            st.pop();
        }
        reverse(result.begin(),result.end());
        return result;
    }
};

方法二:直接用字符串作为栈,用字符串里的方法push_back和pop_back模拟出栈入栈,这样省去了栈要转为字符串的操作,也不用再反转。

class Solution {
public:
    string removeDuplicates(string s) {
       string result;
       for(int i = 0; i < s.size(); i++){
        if(result.empty() || result.back() != s[i]){
            result.push_back(s[i]);
        }else{
            result.pop_back();
        }
       }
       return result;
    }
};

(六)逆波兰表达式求值

用栈解决,当遇到运算符,将栈顶的元素pop出来两个,按照运算符进行运算,直到所有的元素都被遍历过,输出栈顶元素,即为运算结果

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(int i = 0; i < tokens.size(); i++){
          //遇到运算符号
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"){
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop();
                if(tokens[i] == "+") st.push(num2 + num1);
                if(tokens[i] == "-") st.push(num2 - num1);
                if(tokens[i] == "*") st.push(num2 * num1);
                if(tokens[i] == "/") st.push(num2 / num1);
            }else{
              //这里注意因为vector里面存储的是string,要用stoi函数转为int
                st.push(stoi(tokens[i]));
            }
        }
        return st.top();
    }
};

(七)滑动窗口最大值

滑动窗口的移动过程:队列

pop() :前面需要遗弃的元素

push() :后面需要加入的元素

getMaxValue() :每次获得队列中所有元素的最大值

实现单调队列(不同于优先级队列)

只需要维护滑动窗口中可能成为最大值的序列

①如果push入的元素前面的元素都比该元素小,则前面的元素都pop出去,只保留该元素在队列中

②如果push入的元素前面的元素都比该元素大,则将该元素直接push进队列

③每次滑动窗口后移,如果需要弹出的元素和队头元素相同,则pop队头元素,否则就不要pop(之前因为太小已经被pop出去了),只需要处理后面待push的元素,重复过程①②

使用deque为数据结构构造单调队列(C++中栈和队列底层实现逻辑都是deque)

deque<int> que; //deque为双向队列,左右都可出入元素
//val为需要pop掉的元素
pop(int val){
  //判断需要pop的元素是和构造的单调队列的队头元素一致,此时需要弹出的元素为单调队列中的最大元素,所以在队头
  if(!que.empty() && val == que.front()){
    que.pop_front();
  }
push(int val){
  //对队列中的元素判断的前面都要先判断队列是否为空
  //将队尾元素一直弹出,直到队尾元素比待push的元素大为止,再将该元素push进入队伍
  while(!que.empty() && val > que.back()){
    que.pop_back();
  }
  que.push_back(val);
}
  //返回队首维护的最大元素
getMaxValue(){
  return que.front();
} 
class Solution {
public:
    deque<int> que;
    void pop(int val){
        if(!que.empty() && que.front() == val){
            que.pop_front();
        }
    }
    void push(int val){
        while(!que.empty() && que.back() < val){
            que.pop_back();
        }
        que.push_back(val);
    }
    int getMaxValue(){
        return que.front();
    }
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        int i = 0;
        for(; i < k; i++){
            push(nums[i]);
        }
        result.push_back(getMaxValue());
        for(; i < nums.size(); i++){
            push(nums[i]);
            pop(nums[i-k]);
            result.push_back(getMaxValue());
        }
        return result;
    }
};

(八)前K个高频元素

两个难点:

①求数组每个元素求频率

②排序

使用map存放元素和频率,key为元素,value为出现的次数

按照value对元素排序,找到前k个

没必要对所有元素排序(nlogn),维护一个数据结构——大顶堆/小顶堆

大顶堆:根节点最大

小顶堆:根节点最小

用堆遍历map,只维护前k个元素的排序

如果用大顶堆,每次pop出的元素为堆顶的元素,这是最大的元素,等到数组所有元素遍历过后,大顶堆里留下的是前k个低频元素,与题意想左。

所以用小顶堆,每次pop出堆顶较小的元素,那么最后留下的是最大的元素。

最后把value排序后对应的key输出。

时间复杂度:
遍历数组:n

在小顶堆里加入元素:logk(因为小顶堆维护是二叉树,二叉树插入节点时间复杂度为logn)

C++中优先级队列的底层实现为堆,可以自定义大顶堆和小顶堆

map<int, int>
for(i = 0; i < nums.size; i++){
  map[nums[i]] ++;
}
//定义优先级队列,小顶堆,cmp()自定义排序方式
priority_que(<key,value> cmp()){
  for(map:: it<key,value>){
    que.push(it);
    //只维护前k个元素
    if(que.size > k) que.pop();
  }
  vector<int> result;
  //倒序输出,因为小顶堆里面pop出来的顺序是由小到大
  for(int i = k-1; i >= 0; i--){
    result[i] = que.top().first();
}
class Solution {
public:
    class mycomparator{
        public:
          bool operator()(const pair<int,int>& lhs, const pair<int,int>& rhs){
            //less是大顶堆,greater是小顶堆,这里构建小顶堆
            return lhs.second > rhs.second;
          }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> numMap;
        for(int i = 0; i< nums.size(); i++){
            numMap[nums[i]]++;
        }
        //priority_queue<type,content,cmp>,最小堆排序
        priority_queue<pair<int,int>, vector<pair<int,int>>,mycomparator> pri_que;
        //用迭代器遍历unordered_map
        for(unordered_map<int,int>::iterator it = numMap.begin(); it != numMap.end(); it++){
            pri_que.push(*it);
            //只维护前k个元素
            if(pri_que.size() > k){
                pri_que.pop();
            }
        }
        vector<int> result(k);
        for(int i = k-1; i >= 0; i --){
            result[i] = pri_que.top().first; //输出的map中的key值
            pri_que.pop();
        }
        return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值