代码随想录day11(栈与队列)

50. 逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

题目链接/文章讲解/视频讲解:代码随想录

逆波兰表达式(后缀表达式)

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long>st;
        for(const string& s:tokens)
        {
            if(isdigit(s[0])||(s.size()>1&&s[0]=='-'))
            {st.push(stoll(s));}//表示遇见运算符直接压入栈
            else //表示遇见操作符
            {
                long long a=st.top();st.pop();
                long long b=st.top();st.pop();
                long long all=0;
                if(s=="+")    all=a+b;
                else if(s=="-")    all=b-a;
                else if(s=="*")    all=a*b;
                else if(s=="/")    all=b/a;
                st.push(all);
            }
        }
        return st.top();
    }
};

将逆波兰式转化为中缀表达式过程:遇到数字进栈,遇到运算符使栈顶的前两个数字出栈,第一个出站的数字位于右运算位,后出栈的存在于左运算位,然后计算运算结果在压入栈。

需要完善的相关基础语法知识:

1.单引号只能表示单个字符,单引号只能用于表示单个字符,即 char 类型。

char ch = 'AB';  // ❌ 错误!单引号只能包含一个字符

双引号用于表示字符串,即 stringconst char*

char ch = "A";  // ❌ 错误!"A" 是字符串,不能赋值给 char

2.

for (string s : tokens) { ... }

这样 s 不是引用,而是 tokens 里元素的拷贝,会导致额外的性能损耗

for (const string& s : tokens) { ... }

const避免修改s,使更高效

3.stoll的用法,使字符串转化 long long类型的整数

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str1 = "12345";
    string str2 = "-67890";
    
    long long num1 = stoll(str1);
    long long num2 = stoll(str2);

    cout << num1 << endl;  // 输出 12345
    cout << num2 << endl;  // 输出 -67890

    return 0;
}

239. 滑动窗口最大值 

单调队列的应用

题目链接/文章讲解/视频讲解:代码随想录

队列和双端队列的区别

普通队列(Queue)
  • 类似于排队买票:
    • 只能从 后面(队尾) 加人(push)。
    • 只能从 前面(队头) 取票(pop)。
queue<int> q;
q.push(1); // [1]
q.push(2); // [1, 2]
q.push(3); // [1, 2, 3]
q.pop();   // [2, 3]  (只能从前面移除)
双端队列(Deque)
  • 类似于 双向进出的队伍
    • 既可以从 前面 进、也可以从 后面 进。
    • 既可以从 前面 出、也可以从 后面 出。
      deque<int> dq;
      dq.push_back(1);  // [1]
      dq.push_back(2);  // [1, 2]
      dq.push_front(0); // [0, 1, 2]
      dq.pop_back();    // [0, 1]  (从后面移除)
      dq.pop_front();   // [1]     (从前面移除)
      

       暴力解法的缺点

      如果你每次滑动窗口后,都要重新找一遍最大值,那就很慢了:

    • 每次都要遍历 k 个数,整个数组长度为 n,时间复杂度是 O(n*k),会超时!
    • 我们需要更聪明的方法!

    •  我们的聪明策略 —— 用“队伍管理”技巧!

      我们可以用一个 双端队列(deque) 来维护 窗口内可能成为最大值的元素索引,确保:

    • 队列里的元素单调递减(队头存最大值)。
    • 只存有可能成为最大值的索引,没用的直接踢掉!
    •  形象比喻

      想象你是一名篮球教练,你要在一场 3V3 的比赛中,每次从一排球员里挑选最强的球员上场:

    • 球员(数字)按照体能(值)排队。
    • 每次只能选择连续的 k 个球员。
    • 你要管理好球队阵容,保证最强球员始终站在队伍前列(deque 头部)!
    • 如果一个新来的球员比你队伍里最弱的球员强,那就把弱的踢掉,保证队伍单调递减!
    • class Solution {
      public:
          vector<int> maxSlidingWindow(vector<int>& nums, int k) {
              deque<int>q;//用双端队列来存储滑动窗口有可能最大的下标,保证头部元素下标代表的数值最大
              vector<int>res;//用数组来存储最后结果,为一系列数字
              for(int i=0;i<nums.size();i++)
              {
                  if(!q.empty()&&q.front()<i-k+1)//i-k+1代表窗口的最左端
                  {
                   q.pop_front();//删除已经越界的数组下标
                  }
                  while(!q.empty()&&nums[i]>nums[q.back()])
                  {
                      q.pop_back();
                  }//替换掉队伍中最弱的元素,可以不停的替换删除
                  q.push_back(i);
                if(i>=k-1)//代表窗口已经形成
                {
                 res.push_back(nums[q.front()]);//记录单调队列的最大值
              }
              }
              return res;//经历完所有循环,返回最终结果
          }
      };
      
      

       为什么是 O(n) 复杂度?

      每个元素:

    • 最多入队一次
    • 最多出队一次
    • 整个 nums 只遍历了一遍,每个元素只操作 O(1) 次,因此时间复杂度是 O(n)

347.前 K 个高频元素 (有点难度,可能代码写不出来,一刷至少需要理解思路)

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

题目链接/文章讲解/视频讲解:https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html

我们需要找到数组 nums 中出现频率最高的 k 个元素,可以用哈希表 + 小顶堆来高效解决。

在 C++ 中,默认的 priority_queue 是最大堆,但我们可以使用 greater<> 或者 vector + make_heap() 等方式 实现最小堆

C++ STL 提供的 priority_queue 默认是大顶堆,即 堆顶元素最大

#include <iostream>
#include <queue>
using namespace std;

int main() {
    priority_queue<int> maxHeap; // 默认是大顶堆(最大堆)

    maxHeap.push(3);
    maxHeap.push(1);
    maxHeap.push(5);
    
    cout << "最大堆的堆顶:" << maxHeap.top() << endl; // 输出 5

    return 0;
}

2. 使用 greater<> 创建最小堆

方式 1:priority_queue<T, vector<T>, greater<T>>

#include <iostream>
#include <queue>
using namespace std;

int main() {
    priority_queue<int, vector<int>, greater<int>> minHeap; // 最小堆

    minHeap.push(3);
    minHeap.push(1);
    minHeap.push(5);

    cout << "最小堆的堆顶:" << minHeap.top() << endl; // 输出 1

    return 0;
}
  • 第一参数 int: 表示存储的类型
  • 第二参数 vector<int>: 指定底层容器(默认就是 vector
  • 第三参数 greater<int>: 让 priority_queue 变成 最小堆(堆顶最小)
  •  pair 作为最小堆,在 哈希表统计频率 + 最小堆 方案中,我们会用 pair<int, int> 存储 (频率, 元素),并按 频率排序
  • priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> minHeap;
    
    #include <iostream>
    #include <queue>
    using namespace std;
    
    int main() {
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> minHeap;
    
        minHeap.emplace(3, 100); // 频率 3, 数字 100
        minHeap.emplace(1, 200); // 频率 1, 数字 200
        minHeap.emplace(2, 300); // 频率 2, 数字 300
    
        cout << "最小堆的堆顶:" << minHeap.top().second << endl; // 输出 200(因为频率最小是1)
    
        return 0;
    }
    

    为什么使用 emplace() 而不是 push()

    如果使用 push(),需要先手动创建一个 pair<int, int>,然后再插入:

  • minHeap.push(make_pair(3, 100));
    

    为什么用 vector?

  • unordered_map<int, int> 不能直接排序,所以需要转换成 vector 便于排序或建堆

 解决方案

  1. 统计频率:使用 unordered_map 统计每个数字出现的次数。
  2. 使用小顶堆(优先队列):维护 k 个高频元素。
    • 当堆的大小超过 k 时,弹出最小的元素,确保堆内始终是前 k 大的频率元素。
  3. 提取结果:取出堆中的元素,返回即可。
    class Solution {
    public:
        vector<int> topKFrequent(vector<int>& nums, int k) {
            unordered_map<int,int>freqmap;//unordered_map不能直接排序
            for(int i=0;i<nums.size();i++)
            {
                freqmap[nums[i]]++;///形成pair<int,int>(数字,频率)的形式
            }
            priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>>minHeap;//最小堆
            //为了便于排序,需要转化为(频率,数字)的形式
            
            for(const auto&[key,value]:freqmap)
            {
              minHeap.emplace(value,key);
              if(minHeap.size()>k)
              {
                minHeap.pop();//维护堆的大小不超过k
              }
            }
            vector<int>res;//双端数组存储最终结果push_back,pop_back
            while(!minHeap.empty())
            {res.push_back(minHeap.top().second);
            minHeap.pop();
            }
    return res;
        }
    };

    采用最小堆的原因:当堆中元素超过k时,弹出顶部元素,即最小元素,维护的堆中的频率前k大的元素,最终再将堆中存储的大频率元素一个个弹出。

  4. 补充知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值