【代码随想录】【LeetCode】自学笔记07 - 栈和队列

总结

基础补牢:【https://blog.csdn.net/tham_/article/details/44733101】

根据【http://c.biancheng.net/view/3354.html】,在栈讲义里看到了,链表插入头结点真的是在dummyhead和第一个之间插入的;在队列讲义里看到了双指针…

栈和队列都是【容器适配器】,可以出一道面试题:栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:
陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布不确定。
陷阱2:缺省情况下,默认底层容器是deque,那么deque的在内存中的数据分布是什么样的呢? 答案是:不连续的,下文也会提到deque。

递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中
两种特殊的队列:滑动窗口最大值问题:单调队列 + 求前 K 个高频元素:优先级队列

  1. C++中stack 和 queue 是容器么?
    container adapter(容器适配器)
    栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
    栈和队列的底层实现可以是vector,deque,list 都是可以的,时间复杂度是O( 1 )。

  2. 我们使用的stack 和 queue 是属于哪个版本的STL?
    SGI STL

  3. 我们使用的STL中stack 和 queue 是如何实现的?
    默认是以deque实现的

  4. stack 提供迭代器来遍历stack 和 queue 空间么?

232.用栈实现队列

必须用两个栈才能实现队列;
画图表示【很有必要】!

 /** 构造函数,必须有吗?(我知道这个题里有用到但是平时呢) */
//啥也没有,分分号都没有
//while最后记得推进循环进程,多次犯程序空转的错误
//this就是主函数里构造并使用的那个MyQueue吧?
class MyQueue {
public:
    MyQueue() {
        // shuru;
        // shuchu;
    }
    
    void push(int x) {
        shuru.push(x);
    }
    
    int pop() {
        if(shuchu.empty()){
            while( !shuru.empty()){
                shuchu.push(shuru.top());
                shuru.pop();
            }
        }
        int res = shuchu.top();
        shuchu.pop();
        return res;
    }
    
    int peek() {
        int res = this->pop();//这就是没有参数的时候用this嘛
        shuchu.push(res);
        return res;
    }
    
    bool empty() {
        return(shuru.empty() && shuchu.empty());
    }
// private:放到公共变量里(并且在最前面),然后构造函数为空(why?因为题目要求的构造函数不需要变量?)
        stack<int> shuru;
        stack<int> shuchu;
};

/**
 * 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();
 使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系。
 */

队列输入先后:4-3-2-1
back ············· ->························· front
——————————————————
-> 1 ············ 2 ············ 3············ 4·····->
——————————————————
push ············································ pop

225. 用队列实现栈

queue的常见用法:pop(), push( xxx ), front(), back(), empty();
stack的常见用法:pop(), push( xxx ), top(), empty();
【代码随想录】【LeetCode】自学笔记07 - 栈和队列_第1张图片来源[http://c.biancheng.net/view/479.html]

class MyStack {
public:
    queue<int> st;
    MyStack() {

    }
    
    void push(int x) {
        st.push(x);
    }
    
    int pop() {
        ///重点是这个函数,要把队列的最后一个元素删掉而不是简单的获取,所以只用back不够
        for(int i = 0; i< st.size()-1; i++){
            st.push(st.front());
            st.pop();
        }
        int res = st.front();
        st.pop();
        return res;
    }
    
    int top() {
        return st.back();
    }

    bool empty() {
        return (st.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();
 队列模拟栈,其实一个队列就够了,那么我们先说一说两个队列来实现栈的思路。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用又来备份的!
一个队列在模拟栈弹出元素的时候,只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。
 */

20.有效的括号

括号匹配是使用栈解决的经典问题。
后遇到的左括号要先闭合.

【代随】用了纯纯的栈,【力扣】用的栈+哈希表,~~分别默写了一下自己随便改改答案导致爆炸的情况可太多次了orz~

//【1】【代随】解法:栈
//一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
class Solution {
public:
    bool isValid(string s) {
        stack<int> st;
        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(']');
            // 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
            // 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
            else if (st.empty() || st.top() != s[i]) return false;
            else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
        }
        // 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
        return st.empty();
    }
};
// // //【2】来源:力扣(LeetCode)
/*为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。*/
class Solution{
public:
bool isValid(string  s){
    // int n = s.size();
    unordered_map<char, char> pairs = {
        {')', '('},
        {']', '['}, 
        {'}', '{'}
    };
    stack<char> stk;
    if (! s.size()%2 )return false;
    for (int i = 0; i <s.size(); i++){
        if (pairs.count(s[i])){
            if (stk.empty() || stk.top() != pairs[s[i]]) return false;//stk.empty() ||必须加!!不加有可能移除(空了就没有top了)
            else stk.pop();
        }else stk.push(s[i]);

    }
    return stk.empty();
}
};

//【3】自己写的
//char是字符类型, String是字符串类型;String内部用来存储的结果是一个char字符数组。
//stack st 也可以....
//if语句执行行只有1行的话可以不加{},直接跟在if()后面;
class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for(int i = 0;i <s.size(); i++ ){
            if(s[i]=='[' || s[i]=='{' || s[i]=='(') st.push(s[i]);
            else{
                if(st.empty()) return false;

                else if((s[i]==']' && st.top()=='[') 
                || (s[i]=='}' && st.top()=='{') 
                || (s[i]==')' && st.top()=='(') ) st.pop();
                
                else return false;
            }
        }
        if(!st.empty()) return false;
        return true;

    }
};
//由于栈结构的特殊性,非常适合做对称匹配类的题目。
//要写代码之前要分析好有哪几种不匹配的情况,这里有三种不匹配的情况:左多了、右多了、不多不少但不成对(类型不匹配)

1047. 删除字符串中的所有相邻重复项

匹配问题都是栈的强项
拿字符串直接作为栈,(用字符串模拟栈)这样省去了栈还要转为字符串的操作,但是相应函数就是vector< char >对应的pop_back 和push_back

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

150. 逆波兰表达式求值

在上一题1047.删除字符串中的所有相邻重复项 提到了 递归就是用栈来实现的。

stoi()函数——将数字字符转化位int输出;使用之前要包含头文件#include< string >

347.前 K 个高频元素

!还没搞懂的知识点:
小顶堆
class中class
bool operator()()
priority_queue< 123 >
unordered_map:: iterator it

思路
这道题目主要涉及到如下三块内容:
要统计元素出现频率
对频率排序
找出前K个高频元素

首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列

(1)什么是优先级队列呢?

其实就是一个披着队列外衣的,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

(2)什么是呢?

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。可以把堆分为大顶堆和小顶堆。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素****)小顶堆(堆头是最小元素),如果懒得自己实现的话,就**直接用priority_queue(优先级队列)**就可以实现了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
堆的这种特性非常的有用,堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。

注意:从栈顶top()处pop

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

为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
此时要思考一下,是使用小顶堆呢,还是大顶堆?有的同学一想,题目要求前 K 个高频元素,那么果断用大顶堆啊。那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?
所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素(从队头)弹出,最后小顶堆里积累的才是前k个最大元素

// 链接:https://leetcode.cn/problems/top-k-frequent-elements/solution/c-xiao-bai-you-hao-you-xian-dui-lie-de-j-53ay/
// 无标注版,注意一些pair的使用、priority_queue的使用及其排序结构体的定义
class Solution{
    public:
    vector<int> topKFrequent(vector<int>& nums, int k){
        unordered_map<int,int> map;
        for(int i = 0; i< nums.size(); i++) map[nums[i]]++;///默认每个key的val初始是0所以可以直接++   
        
        struct myComparison{
            bool operator()(pair<int, int>& p1, pair<int, int>& p2) { return p1.second > p2.second; }
            // /函数有(),class和struct无()!!!operator() !!!NO second()!!!
        };   

        priority_queue <pair<int,int>, vector<pair<int,int>>, myComparison> q;  ///NO myconmarison()

        for(auto& a:map){
            q.push(a);
            if(q.size()>k) q.pop();
        }

        vector<int> res;
        while(!q.empty()){
            res.push_back(q.top().first);
            q.pop();
        }
        reverse (res.begin(), res.end());
        return res;
    }


};
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    //1.map记录元素出现的次数
        unordered_map<int,int>map;//两个int分别是元素和出现的次数
        for(auto& c:nums){
            map[c]++;
        }
    //2.利用优先队列,将出现次数排序
        //自定义优先队列的比较方式,小顶堆;注意其形式不是在小class里就是在小struct里,都是无()有{};  
        struct myComparison{
            bool operator()(pair<int,int>&p1,pair<int,int>&p2){
                return p1.second>p2.second;//小顶堆是大于号
            }
        };
        //创建优先队列
        priority_queue<pair<int,int>,vector<pair<int,int>>,myComparison> q;
        //遍历map中的元素
        //1.管他是啥,先入队列,队列会自己排序将他放在合适的位置
        //2.若队列元素个数超过k,则将栈顶元素出栈(栈顶元素一定是最小的那个)//并且从栈顶top()处pop,使得最小的被弹出
        for(auto& a:map){
            q.push(a);
            if(q.size()>k){
               q.pop(); 
            }
        }
        //将结果导出,注意要翻转
        vector<int>res;
        while(!q.empty()){
            res.push_back(q.top().first);//emplace
            q.pop();
        }
        reverse(res.begin(), res.end());
        return res;

    }
};

你可能感兴趣的:(代码随想录_学习记录,Leecode学习记录,c++,leetcode,栈)