队列及其leetcode的一些练习

队列

1. 简要介绍

队列是先进先出的数据结构,跟栈一样,队列可以用数组来实现,也可以用链表来实现。用数组实现的栈叫作顺序栈,用链表实现的栈叫作链式栈。同样,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列

常用操作:

(1)数据存取:

  • push(elem); //往队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

(2)大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小

2. Leetcode练习题

leetcode上关于队列的题目:225,1047,150,239,347

2.1 225.用队列实现栈

题目链接:225. 用队列实现栈 - 力扣(LeetCode)

分析:这种直接画图。用两个队列和一个队列都行,因为另一个队列的作用就是作为缓冲区,队列操作也比栈方便。

class MyStack {
public:
// push pop back front
    queue<int> dIn;
    queue<int> dOut; //这其实就是一个缓冲的stack
    MyStack() {

    }

    void push(int x) {
        dIn.push(x);
    }

    // 1.用两个queue实现的
    // int pop() {
    //     int i = 0, n = dIn.size() - 1;
    //     while(i < n) {
    //         dOut.push(dIn.front());
    //         dIn.pop();
    //         i++;
    //     }
    //     int ret = dIn.front(); //最后一个数就是要删除的数字
    //     dIn.pop();
    //     while(! dOut.empty()) {
    //         dIn.push(dOut.front());
    //         dOut.pop();
    //     }
    //     return ret;
    // }

    // 2.用一个queue实现的
    int pop() {
        int i = 0, n = dIn.size() - 1;
        while(i < n) {
            dIn.push(dIn.front()); //直接把队头元素加到队尾
            dIn.pop();
            i++;
        }
        int ret = dIn.front(); //最后一个数就是要删除的数字
        dIn.pop();
        return ret;
    }

    int top() {
        return dIn.back();
    }

    bool empty() {
        return dIn.empty();
    }
};

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

题目链接:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

分析:用一个栈就行,每次加入的时候进行一次比较,时间复杂度O(n)。最后的结果要reverse。

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> st;
        int i = 0;
        while(i < s.length()) {
            if(st.empty() || s[i] != st.top()) {
                st.push(s[i]);
            } else {
                st.pop();
            }
            i++;
        }
        string ret;
        while(! st.empty()) {
            ret.push_back(st.top());
            st.pop();
        }
        reverse(ret.begin(), ret.end()); // 此时字符串需要反转一下
        return ret;
    }
};

2.3 150.逆波兰表达式求值

题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)

分析:遍历一遍,边遍历边进行计算。本题中得用long long 类型,之前用过stoi函数,是把字符串转化为int类型,这题用的是stoll函数,把字符串解析为long long 类型。

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        // stoll函数将在函数调用中作为参数提供的字符串转换为long long int。
        // 它解析str并将其内容解释为指定基数的整数,并将其作为long long int类型的值返回。
        stack<long long> st;
        for(int i = 0; i < tokens.size(); i++) {
            // 注意单引号是字符型,双引号是字符串型,把引号用错了一直报错
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                long long num1 = st.top();
                st.pop();
                long long 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 {
                st.push(stoll(tokens[i]));
            }
        }
        return st.top();
    }
};

2.4 239.滑动窗口最大值

题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)

分析:用一个递减的双端队列dq来做:滑动窗口也就是遍历nums,如果加入的值大于dq中最后一个值,就pop它,保持dq的递减;这样dq的front就是要求的窗口中的最大值,加入到ans数组中;注意要把超出窗口的值pop掉。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans; //保存结果
        deque<int> dq; //用一个双端队列,该队列是递减队列

        for(int i = 0; i < nums.size(); i++) {
            //保持递减
            while(! dq.empty() && dq.back() < nums[i]) 
                dq.pop_back();
            dq.push_back(nums[i]);

            //将窗口中最大的数也就是front的值加入ans中
            if(i >= k - 1) {
                ans.push_back(dq.front());
                //超出窗口的数pop
                if(nums[i - k + 1] == dq.front()) {
                    dq.pop_front();
                }
            }
        }
        return ans;
    }
};

2.5 347.前 K 个高频元素

题目链接:347. 前 K 个高频元素 - 力扣(LeetCode)

分析:先用unordered_map统计元素出现的频率,之后用小顶堆,超出k之后的就pop掉,保留的就是前k个高频元素。注意要小顶堆的自定义函数中return的是左 > 右,大顶堆是左 < 右!优先级队列的定义与其他的正好是反过来的,要特别注意这点。

补充:

  • 与基于比较的排序算法时间复杂度 O(nlogn)相比,使用堆,优先队列复杂度可以下降到 O(nlogk),在总体数据规模 n 较大,而维护规模 k 较小时,时间复杂度优化明显。

  • 时间复杂度分析:O(nlogk)。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这里维护堆的数目是 k,所以这一系列操作的时间复杂度是 O(nlogk)的;因此,总的时间复杂度是 O(nlog⁡k)。

  • 优先级队列:
    priority_queue<Type, Container, Functional>;

    • Type是要存放的数据类型;

    • Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque;

    • Functional是比较方式/比较函数/优先级。

    priority_queue<Type>;'

    此时默认的容器是vector,默认的比较方式是大顶堆less。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> map;
        vector<int> ans;
        //先遍历一遍记录元素出现的频率 O(n)
        for(auto c : nums) {
            map[c] ++;
        }
        //自定义比较
        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;
        
        //多于k的元素pop出去 O(nlogk) 
        for(auto c : map) {
            q.push(c); 
            if(q.size() > k) {
                q.pop();
            }
        }
        //加入到结果中 O(k)
        while(! q.empty()) {
            ans.push_back(q.top().first);
            q.pop();
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值