基础知识:

栈与队列都是容器接口,而非容器;
栈与队列可选容器,缺省状态下是deque;
提供push,pop等操作,但不提供送代器,不提供走访功能,因为只能在一边进行插入,弹出操作;

  • 栈的经典题目

1.栈在系统方面有许多应用
Linux命令 cd/a/b/....运用栈的知识,确认进入了a目录;
函数递归必须要在栈中进行
2.匹配问题
例如,leetcode20,有效的括号

class Solution {
public:
    bool isValid(string s) {
        stack<int> tmp;
        for (int i=0;i<s.size();i++)    //此题利用栈进行匹配,若全部匹配成功,最后栈为空,重点要考虑有哪些不匹配的情况,共有三种
        {
            if (s[i]=='(')
            tmp.push(')');
            else if(s[i]=='[')
            tmp.push(']');
            else if(s[i]=='{')
            tmp.push('}');
            else if(tmp.empty())    //1.s没遍历玩,栈就空了,说明右边多了;
            return false;
            else if(tmp.top()!=s[i]) //2.匹配错误
            return false;
            else
            tmp.pop();
        }
        return tmp.empty();   //3.遍历完栈内还有元素,说明push的多余pop的,说明左边多了。
    }
};

例如1047,删除字符串中所有相邻重复项

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> exm;
        exm.push(s[s.size()-1]);
        for (int i=s.size()-2;i>=0;i--)
        {
            if(exm.empty() || s[i]!=exm.top())  //开始时没写exm.empty(),出现aaa等情况便出错
            {
                exm.push(s[i]);
                continue;
            }
            exm.pop();
        }
        /*if(exm.empty())
        return NULL;*/              //此处不需要这一判断,因为下面可以覆盖这种情况
        int size=exm.size();
        //s.resize(size);
        string sq;
        for (int i=0;i<size;i++)
        {
            //s[i]=exm.top();
            sq.push_back(exm.top());
            exm.pop();
        }
        return sq;
    }
};

150,逆波兰表达式求值,此题思路一样,,另外有点消消乐的感觉,实现代码要比前两道还简单,关键stoi函数的使用之前不清楚

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> numbers;
        for(int i = 0 ; i < tokens.size() ;++i){
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"){
                int res;
                int n2 = numbers.top();
                numbers.pop();
                int n1 = numbers.top();
                numbers.pop();
                
                if(tokens[i] == "+")
                   res = n1 + n2;
                else if(tokens[i] == "-")
                   res = n1 - n2;
                else if(tokens[i] == "/")
                   res = n1 / n2;
                else
                   res = n1 * n2;
                numbers.push(res);
            }else{
                numbers.push(stoi(tokens[i]));  //注意stoi函数
            } 
        }
        return numbers.top();

    }
};
  • 队列经典题目

队列方面遇到了两种值得细究的结构:单调队列,优先级队列
看题239,滑动窗口最大值
(主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来一个单调队列

而且不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。)
-carl

class Solution {
    class mypri           //把pop,push等操作的条件判断放在mypri的构建中
    {
        public:             
            deque<int> t;
            void pop(int value)
            {
                if(!t.empty() && value==t.front())  //pop的两个判断,前一个一般都要写
                t.pop_front();
            }
            void push(int value)
            {
                while(!t.empty() && value>t.back())
                {
                    t.pop_back();
                }
                t.push_back(value);
            }
            int front()
            {
                return t.front();
            }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        mypri test;
        for (int i=0;i<k;i++)       //本题要求每一个k组最大值,所以要先有第一个k组后
        {                           //再进行流程化操作;
            test.push(nums[i]);
        }
        result.push_back(test.front());
        for (int i=k;i<nums.size();i++)
        {
            test.pop(nums[i-k]);
            test.push(nums[i]);
            result.push_back(test.front());
        }
        return result;
    }
};

用deque作为单调队列的底层数据结构,C++中deque是stack和queue默认的底层实现容器(这个我们之前已经讲过),deque是可以两边扩展的,而且deque里元素并不是严格的连续分布的。 -carl

347.前k个高频元素
本题主要有以下几点
1.用unordered_map存放元素及出现的频率
2.用优先级队列实现小顶堆
另外还要注意一下如比较函数等细节

class Solution {
public:
    class mycomparison
    {
        public:
        bool operator()(const pair<int,int>& left,const pair<int,int>& right)
        {
            return left.second>right.second;                                //与快排函数的比较相反
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> t;
        for (int i=0;i<nums.size();i++)
        {
            t[nums[i]]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> p;        //刚接触,pair<>也是一种数据类型
        for (unordered_map<int,int>::iterator it=t.begin();it!=t.end();it++)
        {
            p.push(*it);
            if(p.size()>k)
            p.pop();
        }
        vector<int> result(k);                                 //此处用reserve失效!!!,应该是系统用送代器去打印元素
       // result.reserve(k);
        for (int i=k-1;i>-1;i--)
        {
            result[i]=p.top().first;
            p.pop();
        }
        return result;
    }
};

附上代码随想录中对于这一部分的描述:
什么是优先级队列呢?

其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。

而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?

缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

什么是堆呢?

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。

本题我们就要使用优先级队列来对部分频率进行排序。

为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。

              -carl