跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

数据结构实战:栈、队列、优先级队列与哈希表应用

涵盖数据结构核心模块,包括优先级队列在动态数据流处理中的应用,如中位数查找与 K 大元素;栈与队列的相互实现及循环队列设计;数组去重与多数元素判定技巧;以及哈希表在两数之和、异位词分组等经典问题中的优化方案。通过具体代码示例解析底层逻辑与时间复杂度权衡。

宁静发布于 2026/3/26更新于 2026/5/2214 浏览
数据结构实战:栈、队列、优先级队列与哈希表应用

数据结构实战:核心模块解析

1. 优先级队列

最后一块石头的重量

题目要求模拟石头碰撞的过程,每次取最大的两块相撞。这天然适合使用最大堆(优先级队列)来维护当前石头的重量。

class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> pq;
        for (int e : stones) pq.push(e);
        while (pq.size() > 1) {
            int x = pq.top(); pq.pop();
            int y = pq.top(); pq.pop();
            if (x - y > 0) pq.push(x - y);
        }
        return pq.empty() ? 0 : pq.top();
    }
};

数据流中的第 K 大元素

维护一个大小为 K 的小根堆,堆顶即为第 K 大元素。当新元素加入时,若堆已满且新元素大于堆顶,则弹出堆顶并压入新元素。

class KthLargest {
    priority_queue<int, vector<int>, greater<int>> pq;
    int _k;
public:
    KthLargest(int k, vector<int>& nums) {
        _k = k;
        for (int e : nums) {
            pq.push(e);
            if (pq.size() > _k) pq.pop();
        }
    }
    int add(int val) {
        pq.push(val);
        if (pq.size() > _k) pq.pop();
        return pq.top();
    }
};

前 K 个高频单词

自定义比较器,优先按频率降序,频率相同时按字典序升序。利用哈希表统计词频,再配合优先级队列筛选 Top K。

class Solution {
    using psi = pair<string, int>;
    struct cmp {
        bool operator()(const psi& p1, const psi& p2) {
            if (p1.second == p2.second) return p1.first < p2.first;
            return p1.second > p2.second;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        vector<string> ret(k);
        unordered_map<string, int> hash;
        for (auto& str : words) hash[str]++;
        priority_queue<psi, vector<psi>, cmp> pq;
        for (auto& e : hash) {
            pq.push(e);
            if (pq.size() > k) pq.pop();
        }
        while (!pq.empty()) {
            ret[--k] = pq.top().first;
            pq.pop();
        }
        return ret;
    }
};

数据流的中位数

使用两个堆维护中位数:一个大根堆存较小的一半,一个小根堆存较大的一半。保证两堆大小平衡,即可在 O(1) 时间获取中位数。

class MedianFinder {
    priority_queue<int> left; // 大根堆
    priority_queue<int, vector<int>, greater<int>> right; // 小根堆
public:
    MedianFinder() {}
    void addNum(int num) {
        if (left.size() == right.size()) {
            if (left.size() && num <= left.top()) left.push(num);
            else {
                right.push(num);
                left.push(right.top());
                right.pop();
            }
        } else {
            if (num >= left.top()) right.push(num);
            else {
                left.push(num);
                right.push(left.top());
                left.pop();
            }
        }
    }
    double findMedian() {
        if (left.size() == right.size()) return (left.top() + right.top()) / 2.0;
        return left.top();
    }
};

2. 栈、队列

字符串消除问题

解决这类消除问题,直觉上会想到使用栈结构。不过既然最终需要返回字符串,直接使用 std::string 作为栈来操作可能更便捷,其接口丰富且无需额外转换。

注意边界情况,空串的处理返回值类型需严格匹配题目要求。

#include<iostream>
using namespace std;
int main() {
    string str, st;
    cin >> str;
    for (char ch : str) {
        if (!st.empty() && st.back() == ch) {
            st.pop_back();
            continue;
        }
        st.push_back(ch);
    }
    cout << (st.empty() ? "0" : st);
    return 0;
}

最小栈

定义一个主栈和一个辅助栈。主栈支持常规操作,辅助栈用于存储当前栈内的最小值。每当有新元素入栈,如果它小于等于辅助栈顶,则同步入栈;出栈时若主栈顶等于辅助栈顶,则辅助栈也出栈。

class MinStack {
public:
    MinStack() {}
    void push(int val) {
        _st.push(val);
        if (_minst.empty() || val <= _minst.top()) {
            _minst.push(val);
        }
    }
    void pop() {
        if (_st.top() == _minst.top()) {
            _minst.pop();
        }
        _st.pop();
    }
    int top() { return _st.top(); }
    int getMin() { return _minst.top(); }
private:
    stack<int> _st;
    stack<int> _minst;
};

栈的压入、弹出序列

模拟入栈过程,每压入一个元素就检查是否与弹出序列当前目标匹配。若匹配则立即弹出并移动弹出序列指针。遍历结束后若栈为空,说明序列合法。

class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        int i = 0;
        for (int e : pushV) {
            _st.push(e);
            while (!_st.empty() && _st.top() == popV[i]) {
                _st.pop();
                ++i;
            }
        }
        return _st.empty();
    }
private:
    stack<int> _st;
};

用队列实现栈

利用队列先进先出的特性,通过辅助队列调整顺序。入栈时将所有元素依次移入辅助队列,再将新元素放入队头,最后交换两个队列的角色,确保栈顶始终在队头。

class MyStack {
    queue<int> q;
public:
    MyStack() {}
    void push(int x) {
        int n = q.size();
        q.push(x);
        while (n--) {
            q.push(q.front());
            q.pop();
        }
    }
    int pop() {
        int res = q.front();
        q.pop();
        return res;
    }
    int top() { return q.front(); }
    bool empty() { return q.empty(); }
};

用栈实现队列

利用双栈策略。一个栈专门负责入队,另一个栈专门负责出队。当出队栈为空时,将入队栈的所有元素倒入出队栈,此时队头元素即位于出队栈顶。

class MyQueue {
    stack<int> st1, st2;
    void out() {
        while (st1.size()) {
            st2.push(st1.top());
            st1.pop();
        }
    }
public:
    MyQueue() {}
    void push(int x) { st1.push(x); }
    int pop() {
        if (st2.empty()) out();
        int res = st2.top();
        st2.pop();
        return res;
    }
    int peek() {
        if (st2.empty()) out();
        return st2.top();
    }
    bool empty() { return st1.empty() && st2.empty(); }
};

图书整理 II

这是典型的用双栈模拟队列的场景,逻辑与上述'用栈实现队列'一致。

class CQueue {
    stack<int> st1, st2;
public:
    CQueue() {}
    void appendTail(int value) { st1.push(value); }
    int deleteHead() {
        if (st2.empty()) {
            while (st1.size()) {
                st2.push(st1.top());
                st1.pop();
            }
        }
        int res = -1;
        if (st2.size()) {
            res = st2.top();
            st2.pop();
        }
        return res;
    }
};

设计循环队列

使用数组实现循环队列时,通常多开一个空间以区分队空和队满状态。head 指向队首,tail 指向队尾下一个位置。通过取模运算实现索引回绕。

typedef struct {
    int* a;
    int head;
    int tail;
    int k;
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return obj->head == (obj->tail + 1) % (obj->k + 1);
}

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    pq->a = (int*)malloc(sizeof(int) * (k + 1));
    pq->head = pq->tail = 0;
    pq->k = k;
    return pq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj)) return false;
    obj->a[obj->tail++] = value;
    obj->tail %= obj->k + 1;
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) return false;
    obj->head++;
    obj->head %= obj->k + 1;
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) return -1;
    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) return -1;
    return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k + 1)];
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

3. 数组

删除有序数组中的重复项

采用快慢指针技巧。快指针遍历数组寻找不同元素,慢指针记录不重复元素的写入位置。由于数组已排序,只需比较相邻元素即可。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slow = 1;
        for (int fast = 1; fast < nums.size(); ++fast) {
            if (nums[fast] != nums[fast - 1]) {
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
};

数组中出现次数超过一半的数字

方法一:候选法 维护一个候选值和计数器。遍历数组,若计数为 0 则更新候选值,否则根据是否相等增减计数。若存在众数,最终留下的必是众数。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        int val = 0;
        int count = 0;
        for (int e : numbers) {
            if (0 == count) {
                val = e;
                ++count;
            } else {
                count = val == e ? ++count : --count;
            }
        }
        return val;
    }
};

方法二:排序法 若众数存在,排序后中间位置的元素一定是众数。虽然时间复杂度略高,但代码简洁。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        sort(numbers.begin(), numbers.end());
        return numbers[numbers.size() / 2];
    }
};

4. 哈希表

两数之和

经典的双指针或哈希查找问题。暴力枚举效率低,使用哈希表可以在一次遍历中完成查找,将时间复杂度优化至 O(N)。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); i++) {
            if (hash.count(target - nums[i]))
                return {i, hash[target - nums[i]]};
            else
                hash[nums[i]] = i;
        }
        return {};
    }
};

判定是否互为字符重排

判断两个字符串是否为异位词。可以直接排序后比较,或者使用哈希表(或定长数组)统计字符频次。

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if (s1.size() != s2.size()) return false;
        int hash[26] = {};
        for (auto e : s1) hash[e - 'a']++;
        for (auto e : s2) {
            if (hash[e - 'a']) hash[e - 'a']--;
            else return false;
        }
        return true;
    }
};

存在重复元素

利用集合去重特性,或者排序后检查相邻元素。哈希表方案同样高效,遇到重复直接返回。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> hash;
        for (auto e : nums) {
            if (hash.count(e)) return true;
            else hash.insert(e);
        }
        return false;
    }
};

存在重复元素 II

在检查重复的同时,还需满足下标距离限制。哈希表记录每个数字最近出现的位置,计算差值即可。

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); i++) {
            if (hash.count(nums[i]) && abs(i - hash[nums[i]]) <= k)
                return true;
            else
                hash[nums[i]] = i;
        }
        return false;
    }
};

字母异位词分组

将字符串排序后作为 Key 存入哈希表,相同 Key 的字符串归为一组。这种方法能自动处理异位词的归类。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> hash;
        for (auto& s : strs) {
            string tmp = s;
            sort(tmp.begin(), tmp.end());
            hash[tmp].push_back(s);
        }
        vector<vector<string>> ret;
        for (auto& [a, b] : hash) ret.push_back(b);
        return ret;
    }
};

目录

  1. 数据结构实战:核心模块解析
  2. 1. 优先级队列
  3. 最后一块石头的重量
  4. 数据流中的第 K 大元素
  5. 前 K 个高频单词
  6. 数据流的中位数
  7. 2. 栈、队列
  8. 字符串消除问题
  9. 最小栈
  10. 栈的压入、弹出序列
  11. 用队列实现栈
  12. 用栈实现队列
  13. 图书整理 II
  14. 设计循环队列
  15. 3. 数组
  16. 删除有序数组中的重复项
  17. 数组中出现次数超过一半的数字
  18. 4. 哈希表
  19. 两数之和
  20. 判定是否互为字符重排
  21. 存在重复元素
  22. 存在重复元素 II
  23. 字母异位词分组
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 阿里 Qwen-Image-2512 开源评测:真实质感与多场景应用实践
  • 飞算 JavaAI 插件辅助生成 Java 项目实战
  • 前端可访问性开发指南
  • 111 页全面综述:大模型评测体系与未来展望
  • SpringBoot 整合 Langchain4j RAG 技术深度解析
  • AI 工具泛滥时代,为什么“能力”越来越不值钱?
  • C#读取 Fanuc 机器人数学信号
  • Git 下载速度慢解决方案:国内外镜像地址与安装教程
  • Python 入门实战:猜数字游戏完整教程
  • 机器人灵巧手技术演进市场格局与未来前景
  • MCP 数据加密方法解析:5 大主流算法对比及选型指南
  • 优雅降级 vs 渐进增强:前端兼容策略的“道”与“术”
  • SRC 漏洞挖掘流程及 CNVD 提交指南
  • Python 列表、字典与生成器推导式详解
  • 基于 Excel VBA 与大模型 API 实现用户反馈情感分析自动化
  • 如何用PDF Arranger轻松管理PDF文件:完整操作指南
  • 基于 Python Flask 和 Vue 的动漫周边商城系统设计与实现
  • 通义千问 Qwen-Image-2512 实测:中文提示词秒级生成赛博朋克图
  • Java 剪辑接单报价比价系统技术架构与源码解析
  • LLM(大型语言模型)概念、发展历程与优劣势分析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online