写一些自己LeetCode的刷题过程及总结02(栈与队列)


##leetcode上有2000+的题,不可能都刷完,我的刷题顺序是先分类型,然后再分难度,不断提升,当然过程中也参考了其他大神们的一些建议,光刷题收获不大,最重要的还是不断归纳总结,没事刷两道,坚持写总结其实也挺有意思的。##
##还在不断更新总结!##
##本文仅用来记录自己平时的学习收获##
##有朝一日我也能写出漂亮的代码!##

一、栈与队列

1.1 栈

栈提供push和pop等接口,所有元素必须满足先进后出原则,所以栈不提供迭代器,不像set或map可用迭代器来遍历所有元素。
栈是以底层容器完成所有的工作,对外提供统一的接口,底层容器是可以插拔的(也就是说我们可以决定使用哪种容器来实现栈的功能)。所以在STL中,栈通常不被归为容器,而被归为容器适配器

1.2 队列

队列必须满足先进先出的原则,其它与栈相同,也不提供迭代器,不允许有遍历行为,队列的底层也可以有不同的实现,因此队列也不被归为容器,而被归为容器适配器

1.3 leetcode部分栈与队列题目及代码

232.用栈实现队列
面试题03.02.栈的最小值
225.用队列实现栈
20.有效的括号
1047.删除字符串中的所有相邻重复项
150.逆波兰表达式
239.滑动窗口最大值
347.前k个高频元素(优先队列--小顶堆)
1046.最后一块石头的重量(优先队列-大顶堆)
739.每日温度
752.打开转盘锁

1、232.用栈实现队列

//考察对栈基本操作的掌握程度
class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    /** Initialize your data structure here. **/
    MyQueue() {

    }
    
    /** 将元素 x 推到队列的末尾 **/
    void push(int x) {
        stIn.push(x);
    }
    
    /** 从队列的开头移除并返回元素 **/
    int pop() {
        //当stOut栈为空时将stIn栈中的全部元素都放入stOut中
        if (stOut.empty()) {
            while (!stIn.empty()) {
                int temp = stIn.top();
                stOut.push(temp);
                stIn.pop();
            }
        }
        int ret = stOut.top();
        stOut.pop();
        return ret;
    }
    
    /** 返回队列开头的元素 **/
    int peek() {
        //直接调用pop()
        int ret = this->pop();
        stOut.push(ret);
        return ret;
    }
    
    /** 如果队列为空,返回 true ;否则,返回 false **/
    bool empty() {
        return stIn.size() == 0 && stOut.size() == 0;
    }
};

2、面试题03.02.栈的最小值

//添加一个辅助栈就行
class MinStack {
public:
    stack<int> st;
    stack<int> minSt;//辅助栈
    /** initialize your data structure here. */
    MinStack() {
        minSt.push(INT_MAX);
    }
    
    void push(int x) {
        st.push(x);
        minSt.push(min(minSt.top(), x));
    }
    
    void pop() {
        st.pop();
        minSt.pop();
    }
    
    int top() {
        return st.top();
    }
    
    int getMin() {
        return minSt.top();
    }
};

3、225.用队列实现栈

//考察对队列基本操作的掌握程度
class MyStack {
public:
    queue<int> q1;
    queue<int> q2;
    /** Initialize your data structure here. */
    MyStack() {

    }
    
    /** 将元素 x 压入栈顶。 */
    void push(int x) {
        q1.push(x);
    }
    
    /**  移除并返回栈顶元素。 */
    int pop() {
        int size = q1.size() - 1;
        //将q1导入q2,但是留下q1的最后一个元素
        while (size--) {
            q2.push(q1.front());
            q1.pop();
        }
        int ret = q1.front();
        q1.pop();
        //将q2再导回q1,并将q2清空
        q1 = q2;
        while (!q2.empty()) {
            q2.pop();
        }
        return ret;
    }
    
    /** 返回栈顶元素。 */
    int top() {
        return q1.back();
    }
    
    /** 如果栈是空的,返回 true ;否则,返回 false  */
    bool empty() {
        return q1.size() == 0;
    }
};

4、20.有效的括号

//稍微有一点技巧:
//在匹配的过程中,如果是左括号就让对应的右括号入栈,
//这样在匹配右括号时只用判断是否与栈顶元素相同就可以了
class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for (char ch : s) {
            if (ch == '(') st.push(')');
            else if (ch == '[') st.push(']');
            else if (ch == '{') st.push('}');
            //如果在匹配过程中栈已经为空,或者和栈顶元素不同,返回false
            else if (st.empty() || ch != st.top()) return false;
			//与栈顶元素相同,出栈
            else st.pop();
        }
        //遍历完后,如果栈为空返回true,否则返回false
        return st.empty();
    }
};

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

//这道题肯定想到用栈去解决,但是要注意,如果用stack,最后需要将字符串反转后再返回
//所以这样不如干脆用字符串来模拟栈操作,效率更高
class Solution {
public:
    string removeDuplicates(string s) {
    	//用字符串来模拟栈
        string stk = "";
        for (int i = 0; i < s.size(); ++i) {
            if (i == 0 || s[i] != stk.back()) stk.push_back(s[i]);
            else stk.pop_back();
        }
        return stk;
    }
};

6、150.逆波兰表达式

//逆波兰表达式是一种后缀表达式,不清楚的去查下
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        for (string ch : tokens) {
            if (ch == "+" || ch == "-" || ch == "*" || ch == "/") {
                int num2 = stk.top();
                stk.pop();
                int num1 = stk.top();
                stk.pop();
                if (ch == "+") stk.push(num1 + num2);
                else if (ch == "-") stk.push(num1 - num2);
                else if (ch == "*") stk.push(num1 * num2);
                else if (ch == "/") stk.push(num1 / num2);
            } else {
                stk.push(stoi(ch));
            }
        }
        return stk.top();
    }
};

7、239.滑动窗口最大值

//看到一个大佬用单调队列解的,太巧妙了!
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> ret;
        //先让前k个数入队
        for (int i = 0; i < k; ++i) {
            que.push(nums[i]);
        }
        ret.push_back(que.front());
        for (int i = k; i < nums.size(); ++i) {
            //移除滑动窗口的第一个元素
            que.pop(nums[i - k]);
            //向滑动窗口中加入下一个元素
            que.push(nums[i]);
            //记录当前滑动窗口中的最大值
            ret.push_back(que.front());
        }
        return ret;
    }
private:
    class MyQueue {
    public: 
        //用deque来实现单调队列
        deque<int> que;
        //每次弹出的时候,比较是否等于对头的出口元素,等于才弹出
        void pop(int val) {
            if (!que.empty() && que.front() == val) {
                que.pop_front();
            }
        }
        //如果push的值大于入口元素,先从队列后端弹出,直到push的值小与等于入口的值
        //这样就保证了队列中的值是从大到小的
        void push(int val) {
            while (!que.empty() && val > que.back()) {
                que.pop_back();
            }
            que.push_back(val);
        }
        //对头的值就是最大值
        int front() {
            return que.front();
        }
    };
};

8、347.前k个高频元素

//这道题目主要涉及到如下三块内容:
//    a.要统计元素出现频率
//    b.对频率排序
//    c.找出前K个高频元素
//首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
//然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列。
//优先队列就是一个披着队列外衣的堆,优先级队列内部元素是自动依照元素的权值排列。
//priority_queue默认利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
//堆是一颗完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
class Solution {
public:
    //小顶堆
    class MyComparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> map;
        for (int num : nums) ++map[num];
        //定义小顶堆对频率排序,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, MyComparison> priQue;
        //用固定大小为k的小顶堆,扫描所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); ++it) {
            priQue.push(*it);
            //如果堆的大小超过了k,弹出,保证堆的大小一直为k
            if (priQue.size() > k) {
                priQue.pop();
            }
        }
        //找出前k个高频元素,因为小顶堆先弹出的是最小的,所以倒序输出到数组
        vector<int> ret(k);
        for (int i = k - 1; i >= 0; --i) {
            ret[i] = priQue.top().first;
            priQue.pop();
        }
        return ret;
    }
};

//最开始想到的解法是这样的,只用一个哈希表,感觉有点蠢了
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> m;
        vector<int> vec;
        for (int num : nums) {
            ++m[num];
        }
        int maxTimes = 0;
        for (auto it = m.begin(); it != m.end(); ++it) {
            if (it->second > maxTimes) maxTimes = it->second;
        }
        while (k > 0) {
            for (auto it = m.begin(); it != m.end(); ++it) {
                if (it->second == maxTimes) {
                    vec.push_back(it->first);
                    --k;
                }
            }
            --maxTimes;
        }
        return vec;
    }
};

最后再说明一下C++中的priority_queue.
priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆。

//升序队列
priority_queue<int,vector<int>,greater<int>> q;
//降序队列
priority_queue<int,vector<int>,less<int>>q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。
//其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

9、1046.最后一块石头的重量

//这道题也是用优先队列来解,也就是大顶堆
class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> q;
        for (int stone : stones) {
            q.push(stone);
        }
        while (q.size() > 1) {
            int stone1 = q.top();
            q.pop();
            int stone2 = q.top();
            q.pop();
            if (stone1 != stone2) q.push(stone1 - stone2);
        }
        if (q.empty()) return 0;
        return q.top();
    }
};

10、739.每日温度

class Solution {
public:
    //通过一个单调栈(在这里是递减栈:栈里的元素是递减的)来完成
    
    //具体操作如下:
    //1、遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,
    //所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,
    //直接求出下标差就是二者的距离。

    //2、继续看新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,
    //这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        if (temperatures.size() == 0) return temperatures;
        stack<int> st;
        vector<int> vec(temperatures.size(), 0);
        st.push(0);
        for (int i = 1; i < temperatures.size(); ++i) {
            if (temperatures[i] <= temperatures[st.top()]) {
                st.push(i);
            } else {
                while (!st.empty() && temperatures[st.top()] < temperatures[i]) {
                    vec[st.top()] = i - st.top();
                    st.pop();
                }
                st.push(i);
            }
        }
        return vec;
    }
};

1.4 利用队列进行层序遍历

752.打开转盘锁
994.腐烂的橘子
815.公交路线
690.员工的重要性

1、752.打开转盘锁

//用队列进行BFS
class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        unordered_set<string> set(deadends.begin(), deadends.end());
        if (set.count("0000") == 1) return -1;
        queue<string> que;
        que.push("0000");
        int count = 0;
        while (!que.empty()) {
            int len = que.size();
            for (int i = 0; i < len; ++i) {
                string str = que.front();
                que.pop();
                if (str == target) return count;
                // 处理str周围的八个相邻结点
                for (int j = 0; j < 4; ++j) {
                    //+1 、 -1
                    for (int k = -1; k < 2; k += 2) {
                        string cur = str;
                        cur[j] = (str[j] - '0' + 10 + k) % 10 + '0';
                        if (set.count(cur) != 1) {
                            set.insert(cur);
                            que.push(cur);
                        }
                    }
                }
            }
            // 本层队列中元素处理完成,到达下一转动步数,步数加1
            ++count;
        }
        return -1;
    }
};

2、994.腐烂的橘子

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        queue<pair<int, int>> que;
        int freshNum = 0;
        for (int i = 0; i < grid.size(); ++i) {
            for (int j = 0; j < grid[0].size(); ++j) {
                if (grid[i][j] == 1) ++freshNum;
                else if (grid[i][j] == 2) que.push({i, j});
            }
        }
        int minCount = 0;
        vector<pair<int, int>> dict = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        while (!que.empty()) {
            int len = que.size();
            bool flag = false;
            for (int i = 0; i < len; ++i) {
                auto temp = que.front();
                que.pop();
                for (int j = 0; j < 4; ++j) {
                    int x = temp.first + dict[j].first;
                    int y = temp.second + dict[j].second;
                    if (x >=0 && x < grid.size() && y >= 0 && y < grid[0].size() && grid[x][y] == 1) {
                        grid[x][y] = 2;
                        --freshNum;
                        flag = true;
                        que.push({x, y});
                    }
                }
            }
            if (flag) ++minCount;
        }
        if (freshNum != 0) return -1;
        else return minCount;
    }
};

3、815.公交路线

class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) return 0;
        // 标记站点出现的线路
        unordered_map<int, vector<int>> mp;
        // 标记访问的线路
        vector<bool> visited(routes.size(), false);
        // 记录站点出现的线路
        for (int i = 0; i < routes.size(); ++i) {
            for (int j = 0; j < routes[i].size(); ++j) {
                mp[routes[i][j]].push_back(i);
            }
        }
        queue<int> que;
        que.push(source);
        int step = 0;
        while (!que.empty()) {
            ++step;
            int len = que.size();
            for (int i = 0; i < len; ++i) {
                int temp = que.front();
                que.pop();
                for (int m : mp[temp]) {
                    if (!visited[m]) {
                        for (int n = 0; n < routes[m].size(); ++n) {
                            if (routes[m][n] == target) return step;
                            que.push(routes[m][n]);
                        }
                        visited[m] = true;
                    }
                }
            }
        }
        return -1;
    }
};

4、690.员工的重要性

class Solution {
public:
    int getImportance(vector<Employee *> employees, int id) {
        unordered_map<int, Employee *> mp;
        for (auto &employee : employees) {
            mp[employee->id] = employee;
        }

        int total = 0;
        queue<int> que;
        que.push(id);
        while (!que.empty()) {
            int curId = que.front();
            que.pop();
            Employee *employee = mp[curId];
            total += employee->importance;
            for (int subId : employee->subordinates) {
                que.push(subId);
            }
        }
        return total;
    }
};

1.5 栈与队列总结

1、栈:其实函数的递归就是通过栈来实现的,函数调用时入栈,结束调用时出栈,所以也可以用栈来模拟函数的递归。括号的匹配和后缀表达式等问题都是栈的经典题目。

2、队列:个人感觉队列最经典的题目还是二叉树的层序遍历。上面介绍的两道题滑动窗口的最大值和前k个高频元素这两道题感觉还是比较绕,第一下可能不会想到是队列(想我比较菜确实也没想到…)
(一)滑动窗口的最大值问题,主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来一个单调队列。针对这一问题所设计的单调队列它的push和pop操作需要保持一定原则,这一点在代码中已经说明了。(但这两种操作仅适用于本题目,不同的应用场景单调队列也需要不同的写法)
(二)前k个高频元素问题,引出了优先级队列。优先队列就是一个披着队列外衣的堆,优先级队列内部元素是自动依照元素的权值排列。priority_queue默认利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。堆是一颗完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值