60天刷题之栈与队列 | Day10 Day11

目录

Day10

1 理论基础

2 刷题

2.1 Leetcode232 用栈实现队列

2.2 Leetcode225 用队列实现栈

2.3 leetcode20 有效的括号

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

Day 11

2.5 Leetcode 150. 逆波兰表达式求值

2.7 Leetcode347 前k个高频元素

Day10

1 理论基础

栈和队列是STL(C++标准库)里面的两个数据结构。

栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。

栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。

所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。

栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。

我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的低层结构。

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

SGI STL中 队列底层实现缺省情况下一样使用deque实现的。

STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。

2 刷题

2.1 Leetcode232 用栈实现队列

力扣

题干:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

代码:注意pop( ) 函数的实现

class MyQueue {

public:

    stack<int> stack1;//这个栈模拟队列的压入

    stack<int> stack2;//这个栈模拟队列的压出,先进先出

    MyQueue() {

   

    }

    

    void push(int x) {

        stack1.push(x);

    }

    

    int pop() {

        if(stack2.empty())//stack2为空,把stack1的全倒过来

        {

           while(!stack1.empty())//当stack1压入的元素个数不为0时,全转移到stack2中

           {

             stack2.push(stack1.top());//为了保证先进先出,那么stack2需要把stack1压底的放在最上面

             stack1.pop();

           } 

        }

        //如果stack2一开始不为空,或者为空但经历了上面的if后不空了,直接弹出

        int ret=stack2.top();

        stack2.pop();

        return ret;

    }

    

    int peek() {

        int ret=this->pop();//借用pop函数,把stack2弄好初始化

        stack2.push(ret);//pop函数把ret去除了,重新加入

        return stack2.top();

    }

    

    bool empty() {

        return stack1.empty()&&stack2.empty();//两个都要判断,可能存在push后没做其他操作了,这说明stack1不空,还没来得及到stack2

    }

};

2.2 Leetcode225 用队列实现栈

力扣

方法1:用两个队列实现栈。

队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。

用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。

//方法1:注意pop( )函数的实现,两个队列实现;建议画图模拟下

class MyStack {

public:

    queue<int> Q1;

    queue<int> Q2;//备份用,留下Q1的最后一个元素,把其他元素备份到Q2,然后再还回去

    MyStack() {

    }

    

    void push(int x) {

        Q1.push(x);

    }

    

    int pop() {

        int size=Q1.size();

        size=size-1;//减去1,把要出去的那个元素(即队列中)留下

        while(size)//当size不为0时循环,即把Q1的元素全部备份到Q2,(当然除了最后一个元素)

        {

            Q2.push(Q1.front());

            Q1.pop();

            size--;

        }

        

        int ret=Q1.front();//还剩下一个元素,比如 1 2 3,还剩一个3,符合栈上先进后出的规律

        Q1.pop();

        while(!Q2.empty())

        {

            Q1.push(Q2.front());

            Q2.pop();

        }

        return ret;

    }

    

    int top() {

        return Q1.back();//栈的栈顶元素,即为队列的最后一个元素

    }

    

    bool empty() {

        return Q1.empty();

    }

};

方法2:用一个队列实现栈

一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时去弹出元素就是栈的顺序了。

//方法2:用一个队列实现栈的功能,注意pop( )函数的实现

class MyStack {

public:

    queue<int> Q1;

    MyStack() {

    }

    

    void push(int x) {

        Q1.push(x);

    }

    //循环入队,留下最后一个元素。把Q1前面的元素再次入队,这样位置到了后面(当然入队的同时,把它们原有的位置出队)

    int pop() {

        int size=Q1.size();

        size=size-1;

        while(size--)

        {

            Q1.push(Q1.front());

            Q1.pop();

        }

        int ret=Q1.front();//最后面的那个元素剩下了,此时成为了最前面了

        Q1.pop();

        return ret;

    }

    

    int top() {

        return Q1.back();

    }

    

    bool empty() {

        return Q1.empty();

    }

};

2.3 leetcode20 有效的括号

力扣

题目:给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

思路:

括号匹配是使用栈解决的经典问题。

先来分析一下 这里有三种不匹配的情况

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上
  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false 

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

代码:

//代码随想录题解

    bool isValid(string s) {

        if(s.size()%2!=0)//如果字符串不是偶数

        {

            return false;

        }

        stack<char> stack1;

        for(int i=0;i<s.size();i++)

        {

            if(s[i]=='(')

            {

                stack1.push(')');

            }

            else if(s[i]=='[')

            {

                stack1.push(']');

            }

            else if(s[i]=='{')

            {

                stack1.push('}');

            }

            //第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false

            //第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false

            else if(stack1.empty()||stack1.top()!=s[i])//判断empty必须在前面,也包含开始就是右括号,此时栈为空

            {

                return false;

            }

            else//如果符合条件,把这个符号去掉

            {

                stack1.pop();

            }

        }

        //第一种情况:此时我们已经遍历完了字符串,判断栈是否为空,如果栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true

        return stack1.empty();

}

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

力扣

匹配问题都是栈的强项

思路:本题要删除相邻相同元素,相对于20. 有效的括号 来说其实也是匹配问题,20. 有效的括号 是匹配左右括号,本题是匹配相邻元素,最后都是做消除的操作。

我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?

所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。

然后再去做对应的消除操作。

注意:最后从栈里弹出的元素是倒序的,所以在对字符串进行反转一下,就得到了最终的结果。

代码:

string removeDuplicates(string s) {

        stack<char> stack1;

        for(int i=0;i<s.size();i++)//遍历字符串

        {

            //此时还要保证栈不为空,否则空栈不能求top函数

            if(!stack1.empty()&&stack1.top()==s[i])//如果栈顶元素(即刚入栈的元素)和下一个元素相等,则可以消消乐,即新元素不入栈,栈顶元素出栈

            {

                stack1.pop();

            }

            else //栈为空或者不相等则入栈,if和else对应

            {

                stack1.push(s[i]);

            }

        }

        string str="";

        while(!stack1.empty())

        {

            str+=stack1.top();

            stack1.pop();//记得弹出,老忘!!!

        }

        reverse(str.begin(),str.end());//已经考虑到了stack1为空的情况,此时还是空字符串

        return str;

}

Day 11

2.5 Leetcode 150. 逆波兰表达式求值

力扣

题目:根据 逆波兰表示法,求表达式的值。有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。

适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

代码:

// 栈的经典应用

    //如果遇到操作数,则将数入栈;

    //如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。

    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 temp1=st.top();

                st.pop();

                int temp2=st.top();

                st.pop();//把离栈顶最近的两个元素取到后弹出

                if(tokens[i]=="+")

                {

                    st.push(temp2+temp1);//注意是前面的那个数+ - * /后一个数

                }

                if(tokens[i]=="-")

                {

                    st.push(temp2-temp1);

                }

                if(tokens[i]=="*")

                {

                    st.push((long long)temp2*(long long)temp1);//注意这里int溢出了

                }

                if(tokens[i]=="/")

                {

                    st.push(temp2/temp1);

                }

            

            }

           else

            {

               st.push(stoi(tokens[i]));//注意stoi()函数的使用,把string转换为int数据

            }

        }

        return st.top();

}

2.6 Leetcode239 滑动窗口最大值

待做。

2.7 Leetcode347 前k个高频元素

力扣

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

思路:

这道题目主要涉及到如下三块内容:

  1. 要统计元素出现频率:哈希表
  2. 对频率排序:优先级队列(对map数据排序)
  3. 找出前K个高频元素:小顶堆

优先级队列:

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

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

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

60天刷题之栈与队列 | Day10 Day11_第1张图片

60天刷题之栈与队列 | Day10 Day11_第2张图片

堆:

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

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

这里我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

代码:

 //堆建立时的规则

     class mycmp{

      public:

         bool operator()(const pair<int,int&left,const pair<int,int&right)

         {

             return left.second>right.second;//注意这里与以前不一样,这里建立小顶堆,要return大于,按照次数进行排序(对map数据排序)

         }

     };

    //方法:使用小顶堆,求大数取小顶堆,求小数用大顶堆

    vector<inttopKFrequent(vector<int>& numsint k) {

        unordered_map<int,int> mp;//用哈希表统计各个元素的次数(频率)

        for(int num:nums)

        {

            mp[num]++;

        }

        //建立小顶堆,对map数据按照频率排序

        //定义一个小顶堆,大小为k

        priority_queueint,int>, vectorint,int>>,mycmp> priQ;//priority_queue 

        for(auto iter=mp.begin();iter!=mp.end();iter++)

        {

            priQ.push(*iter);

            if(priQ.size()>k)

            {

                priQ.pop();//把堆上最小的元素去掉(即堆顶)

            }

        }

        //找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组

        vector<intv_ret(k,0);

        for(int i=k-1;i>=0;i--)

        {

            v_ret[i]=priQ.top().first;

            priQ.pop();

        }

        return v_ret; }

你可能感兴趣的:(60天刷题训练营,c++,leetcode,算法)