LeetCode刷题day11——栈和队列part2

150. 逆波兰表达式求值

本题不难,但第一次做的话,会很难想到,所以先看视频,了解思路再去做题 。题目链接/文章讲解/视频讲解
在这里插入图片描述还要掌握字符串转换函数C++字符串转换

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st; // 临时存储运算的栈
        for (auto a : tokens) {
            if (a == "+" || a == "-" || a == "*" || a == "/") {
                // 取两个元素
                long long num1 = st.top();
                st.pop();
                long long num2 = st.top();
                st.pop();
                // 运算,注意num1和2的运算顺序
                if (a == "+")
                    st.push(num2 + num1);
                if (a == "-")
                    st.push(num2 - num1);
                if (a == "*")
                    st.push(num2 * num1);
                if (a == "/")
                    st.push(num2 / num1);
            } else { // 遇到数压入
                st.push(stoll(a));
            }
        }
        int val = st.top();
        st.pop();
        return val;
    }
};

239. 滑动窗口最大值(队列的应用)
(有点难度,可能代码写不出来,一刷至少需要理解思路) 题目链接/文章讲解/视频讲解
卡哥思路:每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。所以要把这三个函数写好,然后滑动窗口即可。
ps:感觉这个思路好理解,,但是写代码的时候就不知道从何下手。

class Solution {
public:
    deque<int> que;
    // 滑动窗口向后移动,前面元素pop

    void pop(int x) {
        // 如果pop的元素是出口处的元素
        // 说明左边要遗弃的元素是之前滑动窗口的最大值
        if (!que.empty() && que.front() == x) {
            que.pop_front();
        }
        // 因为其它情况在push的时候已经把前面的元素剔除了
    }

    // 为什么从back开始 不是front
    void push(int x) {
        // 比你队列后面的元素都大,则这些元素都弹出
        while (!que.empty() && que.back() < x) {
            que.pop_back();
        }
        que.push_back(x);
    }

    // 因为窗口出口处维护的是最大值,所以直接返回
    int front() { 
        return que.front(); 
    }

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        Solution que;//用类定义
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;

    }
};

思路2:自己写了一个根据chatgpt修改(感觉有点绕)。
双端队列(Deque):这里使用一个双端队列来存储数组索引,而不是元素值。这种方法可以快速获取当前窗口的最大值,并且可以快速移除不在窗口内的索引。

维护队列:队列的前端始终包含当前窗口的最大值的索引。当一个新元素比队列中已有元素大时,队列后端较小的元素会被移除,因为它们不再可能是任何未来窗口的最大值。

性能:这种方法的时间复杂度为 O(n),因为每个元素最多被推入和弹出队列一次。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        deque<int> dq; // 双端队列,存储索引

        for (int i = 0; i < nums.size(); i++) {
            // 移除队列中不在滑动窗口内的元素(它们的索引小于当前索引 - k + 1)
            if (!dq.empty() && dq.front() < i - k + 1) {
                dq.pop_front();
            }

            // 移除队列中所有小于当前元素 nums[i]的元素,因为它们不可能是当前或后续窗口的最大值
            while (!dq.empty() && nums[dq.back()] < nums[i]) {
                dq.pop_back();
            }

            // 将当前元素的索引添加到队列末尾
            dq.push_back(i);

            // 从第 k 个元素开始,记录每个窗口的最大值
            if (i >= k - 1) {
                res.push_back(nums[dq.front()]);
            }
        }

        return res;
    }
};

问题: 1、deque和queue区别

347.前 K 个高频元素(大/小顶堆的应用)
(有点难度,可能代码写不出来,一刷至少需要理解思路)大/小顶堆的应用, 在C++中就是优先级队列
本题是 大数据中取前k值 的经典思路。题目链接/文章讲解/视频讲解

前言: 如果用排序的话:
排序算法复杂度
都不满足题目复杂度要求,那么借助哈希表。

统计元素出现的频率用map,但map本身是对key(存储元素)排序的,本题需要val(存储元素出现的次数)排序,所以复杂度是nlogn。那么考虑用优先级队列。优先级队列就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而且优先级队列内部元素是自动依照元素的权值排列。那怎么排序:缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

思路: 大顶堆就是二叉树中根节点最大的元素,小顶堆就是二叉树中根节点最小的元素。因为大顶堆顶部是最大的,而遍历的时候Push进来一个元素就要pop一个元素(为什么push了之后还得pop?),而pop栈顶就把最大的弹出了,那最后留下的就是前k个低频元素了,不是高频,所以要用小顶堆。而因为维护k个所以复杂度是nlogk,优于nlogk。

1、如何定义小顶堆:

 priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

在 C++ 中使用 std::priority_queue 与 std::pair<int, int> 结合是一种常见的技术,尤其是当你需要维护一组具有优先级的元素时。std::pair 允许你将两个可能不同类型的值捆绑在一起,通常在这种情况下,pair 的第一个元素用作优先级(如权重或成本),第二个元素用作携带的数据(如节点标识符或其他有用的信息)。
这行代码定义了一个优先队列,其中:
元素类型:std::pair<int, int>,这里每个 pair 的第一个 int 可能代表某种优先级(例如成本、频率等),第二个 int 可能是与该优先级相关的额外数据(例如某种标识符)。
容器类型:std::vector<pair<int, int>>,这是实际存储队列元素的底层容器。虽然 std::priority_queue 默认使用 std::vector 作为其底层容器,这里显式声明了用于清晰。
比较器类型:mycomparison,这是一个函数对象或者函数指针,用于定义优先队列中元素的优先级顺序。比较器的功能是决定哪些元素应该先于其他元素出队。
2、比较器的实现

  class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;// 更小的数字表示更高的优先级
        }
    };

按照 pair 的 second 值的升序排列。也就是说,队列的顶部(优先出队的位置)将会是 second 值最小的元素。
3、使用迭代器遍历map
在 C++ 中,unordered_map<int, int>::iterator 是一种迭代器类型,用于遍历和访问 std::unordered_map<int, int> 容器中的元素。下面是一个如何使用 unordered_map<int, int>::iterator 的例子:

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<int, int> umap;
    umap[1] = 100;
    umap[2] = 200;
    umap[3] = 300;

    // 使用迭代器遍历unordered_map
    for (std::unordered_map<int, int>::iterator it = umap.begin(); it != umap.end(); ++it) {
        std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
        // 修改值
        it->second += 100;
    }

    // 再次打印修改后的值
    std::cout << "Modified values:" << std::endl;
    for (auto& pair : umap) {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

迭代器提供对元素的直接访问,其中 it->first 是键,it->second 是值。

最终代码:

class Solution {
public:
    // 定义一个比较器,优先级高的先出队(小顶堆)
    // 使其按照 pair 的 second
    // 值的升序排列。也就是说,队列的顶部(优先出队的位置)将会是 second
    // 值最小的元素
    struct mycomparison {
        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 i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率<key,val>排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison>
            pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin();
             it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() >
                k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 如果按照高频到低频的顺序输出
        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first; //first是元素
            pri_que.pop();
        }
        return result;
    }
};

**问题:**其中对于比较器定义不一样。在 C++ 中,struct 和 class 关键字都可以用来定义类。它们之间的主要区别在于默认的访问权限和继承权限:
默认访问权限:
在 struct 中,默认成员和继承的访问权限是 public。
在 class 中,默认成员和继承的访问权限是 private。
这意味着,除非你明确指定,否则 struct 成员和基类都是公开的,而 class 的成员和基类则默认为私有。

总结

栈与队列总结

学习时间

2024.6.17

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值