【力扣刷题 | 第十天】347.前k个高频元素 227 简单计算器

news2024/10/7 8:32:18

前言:

本篇将是最后一篇我们利用栈与队列来解决力扣问题,在下文我们将进入到数这一章,相对应的【夜深人静讲数据结构与算法】专栏中树也会及时更新。

 347. 前 K 个高频元素 - 力扣(LeetCode)

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

 1.哈希表暴力解法:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    // 使用哈希表统计每个元素的出现频率
    unordered_map<int, int> frequency;
    for (auto num : nums) {
        frequency[num]++;
    }

    // 使用桶排序,将出现次数相同的元素放入同一个桶中,并记录出现次数
    vector<vector<int>> buckets(nums.size() + 1);
    for (auto item : frequency) {
        buckets[item.second].push_back(item.first);
    }

    // 从后往前遍历桶,获取出现频率最高的 k 个元素
    vector<int> res;
    for (int i = buckets.size() - 1; i >= 0 && res.size() < k; i--) {
        for (auto elem : buckets[i]) {
            res.push_back(elem);

            if (res.size() == k) {
                break;
            }
        }
    }

    return res;
}
};

我们以1 1 1 2 2 3 为例:
第一步:使用哈希表统计每一个元素的出现频率

第二步:使用桶排序,把出现次数相同的元素放入到一个桶里面,并记录出现次数

详细的来说,我们以出现的次数value为下标,将数字key值放到桶里面。

第三步 .从后向前遍历,获取出现频率最高的k个元素。

因此此时下标就是次数,下标越大出现的次数就越大。因此我们从后向前遍历.

如果下标对应的元素不为0,(出现了符合出现下标次数的元素),我们就输出。

这样就完成了输出整个数组前n个高频数字。


我们可以看出使用哈希数组会对空间造成明显的浪费,因此我们一般倾向于使用第二种算法:

解法2:运用顶堆解法

        大顶堆和小顶堆是两种常见的堆数据结构,都是二叉树形式的数据结构。它们的不同点在于节点之间的大小关系不同。 

        大顶堆是指对于一个父节点,其左右子节点的值都小于等于它的值。也就是说堆顶元素是最大值,在一个大顶堆中,任何一个父节点都比它的子节点大。 

        小顶堆是指对于一个父节点,其左右子节点的值都大于等于它的值。也就是说堆顶元素是最小值,在一个小顶堆中,任何一个父节点都比它的子节点小。 

        堆的操作包括插入、删除和获取堆顶元素等,这些操作都可以在时间复杂度 O(log n) 内完成。它常用于在动态数据中快速找到最大值或最小值,例如快速排序、优先队列等算法和数据结构中。

我们让堆里面就维持k个元素,例如求一个数字中的高频前k个,一般人的思路是:我们就利用大顶堆遍历数组,堆始终维持k个元素,那么最后我们直接输出这个堆顶的所有元素,就可以遍历。但实际上我们要用小顶堆,因为顶堆的top元素只能从堆顶弹出,而我们大顶堆的堆顶元素永远都是最大的,也就是我们如果弹出就会把大元素弹出,因此我们采用小顶堆,一直弹出最小的两个元素,那么我们最后就会得到K个元素。

 这种堆的数据结构是二叉树,而我们整体只需要遍历一次,也就降低了时间复杂度和空间复杂度。

而c++中并没有直接为我们提供堆顶这种数据结构,但是给我们提供了一种底层是大顶堆或者小顶堆的数据结构:优先队列:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> freq;
        for (auto num : nums) {
            freq[num]++;
        }
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
        for (auto iter : freq) {
            pq.push({ iter.second, iter.first });
            if (pq.size() > k) {
                pq.pop();
            }
        }
        vector<int> res(k);
        for (int i = k - 1; i >= 0; i--) {
            res[i] = pq.top().second;
            pq.pop();
        }
        return res;
    }
    
};

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq    的意思:

这是一个使用 STL 中 priority_queue 实现的小顶堆,元素类型为 pair<int, int>,即由一个整型元素和一个对应的出现次数组成的 pair 类型。

priority_queue 的三个参数分别为:

  • - 元素类型:pair<int, int>,表示存放元素的数据类型。
  • - 底层容器类型:vector<pair<int, int>>,表示使用一个 vector 来存储堆中的元素。
  • - 比较函数类型:greater<pair<int, int>>,表示使用一个以 greater 为比较准则的比较函数来实现堆。

其中,greater 表示从大到小排序,而 less 表示从小到大排序。由于我们在找出出现频率最高的前 k 个元素时需要使用小顶堆,这里使用 greater 作为比较准则,即按照出现频率从小到大排序。

 227. 基本计算器 II - 力扣(LeetCode)

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。

这道题的难点就在于编译器在处理运算式子的时候,总是以从左到右的顺序来进行计算的,而忽略了运算符号的优先级,因此本题实际上是在想办法设计处一种算法来处理运算符号的优先级,实际上这种方法已经被人发现了,那就是利用逆波兰表达式。我们先把式子转换成为逆波兰表达式,然后再写逆波兰表达式的计算规则,我们就可以解决此题,而我也有对逆波兰转换进行介绍的文章,里面详细介绍了如何转化为逆波兰表达式以及逆波兰表达式如何进行计算。

【夜深人静学数据结构与算法 | 第二篇】后缀(逆波兰)表达式

class Solution {
public:
    int calculate(string s) {
             stack<int> nums;
    stack<char> ops;
    unordered_map<char, int> prec = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
    int num = 0;
    for (int i = 0; i < s.size(); ++i) {
        if (isdigit(s[i])) {
            num = num * 10 + (s[i] - '0');
            if (i == s.size() - 1 || !isdigit(s[i + 1])) {
                nums.push(num);
                num = 0;
            }
        } else if (s[i] == '(') {
            ops.push('(');
        } else if (s[i] == ')') {
            while (ops.top() != '(') {
                char op = ops.top(); ops.pop();
                int num2 = nums.top(); nums.pop();
                int num1 = nums.top(); nums.pop();
                if (op == '+') nums.push(num1 + num2);
                else if (op == '-') nums.push(num1 - num2);
                else if (op == '*') nums.push(num1 * num2);
                else if (op == '/') nums.push(num1 / num2);
            }
            ops.pop(); // 把左括号弹出
        } else if (prec.count(s[i])) {
            while (!ops.empty() && ops.top() != '(' && prec[s[i]] <= prec[ops.top()]) {
                char op = ops.top(); ops.pop();
                int num2 = nums.top(); nums.pop();
                int num1 = nums.top(); nums.pop();
                if (op == '+') nums.push(num1 + num2);
                else if (op == '-') nums.push(num1 - num2);
                else if (op == '*') nums.push(num1 * num2);
                else if (op == '/') nums.push(num1 / num2);
            }
            ops.push(s[i]);
        }
    }

    while (!ops.empty()) {
        char op = ops.top(); ops.pop();
        int num2 = nums.top(); nums.pop();
        int num1 = nums.top(); nums.pop();
        if (op == '+') nums.push(num1 + num2);
        else if (op == '-') nums.push(num1 - num2);
        else if (op == '*') nums.push(num1 * num2);
        else if (op == '/') nums.push(num1 / num2);
    }

    return nums.top();
    }
};

注意点:
`isdigit` 是 C++ 中用来判断一个字符是否是数字的函数。它的底层实现是通过比较字符的 ASCII 码来判断的。

函数原型为:

int isdigit(int c);

它的参数是一个字符,如果这个字符是一个数字,则返回非零值,否则返回 0。需要包含头文件 #include <cctype>` 才能使用 `isdigit` 函数。

总结:

        合理的利用栈与队列可以解决一些看似棘手的问题,因此我们要学好栈与队列两个数据结构,才可以更好的玩转算法

今天的内容到这里就结束了,感谢大家的阅读。

如果我的内容对你有帮助,请赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/652081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JS】1714- 重学 JavaScript API - Geolocation API

❝ 前期回顾&#xff1a; 1. Page Visibility API 2. Broadcast Channel API 3. Beacon API 4. Resize Observer API 5. Clipboard API 6. Fetch API 7. Performance API 8. WebStorage API 9. WebSockets API 10. Fullscreen API ❞ 本文将深入探讨 Geolocation API 的概念、使…

华为OD机试真题 JavaScript 实现【关联子串】【2023Q1 100分】,附详细解题思路

一、题目描述 给定两个字符串str1和str2&#xff0c;str1进行排列组合只要有一个为str2的子串则认为str1是str2的关联子串&#xff0c;请返回子串在str2的起始位置&#xff0c;若不是关联子串则返回-1。 二、输入描述 qwe dsgfasgfwe 三、输出描述 -1 四、解题思路 读取…

009、体系架构之HTAP

HTAP HTAP技术传统的HTAP解决方案HATP的要求TiDB的HTAP架构TiDB的HTAP特性使用场景 MPP HTAP技术 传统的HTAP解决方案 HATP的要求 可扩展性 分布式事务分布式存储 同时支持OLTP与OLAP 同时支持行存和列存OLTP与OLAP业务隔离 实时性 行存与列存数据实时同步 TiDB的HTAP架构 …

Committer 迎新!这次是来自阿里云的同学

点击蓝字 关注我们 迎新&#xff01; 截至今天&#xff0c;Apache DolphinScheduler 项目在 GitHub 上的 Star 数已突破 10.6K&#xff0c;贡献者人数也突破了 470 人。社区的不断壮大&#xff0c;离不开每位 Contributor 的支持。 最近&#xff0c;Apache DolphinScheduler 又…

AI模型部署实战:利用CV-CUDA加速视觉模型部署流程

本文首发于公众号【DeepDriving】&#xff0c;欢迎关注。 CV-CUDA简介 随着深度学习技术在计算机视觉领域的发展&#xff0c;越来越多的AI算法模型被用于目标检测、图像分割、图像生成等任务中&#xff0c;如何高效地在云端或者边缘设备上部署这些模型是工程师迫切需要解决的问…

Android 13(T) - 智能指针

Android有一套自己的智能指针管理办法&#xff0c;并且将其运用在源码的各个角落&#xff0c;所以学习Media框架之前&#xff0c;我们有必要先了解下Android智能指针。 本节代码源自于Android 13(T)&#xff0c;参考 (aospxref.com) 1 概述 与智能指针相关的总共有5个类&#…

某小厂面试加答案(6.15)

看 Java 面试题就去 www.javacn.site 磊哥新推出《企业面经和答案》栏目&#xff0c;最近会持续更新&#xff0c;欢迎大家订阅此账号查看&#xff0c;或访问 www.javacn.site 查看。 面经来源于牛客&#xff0c;如下图所示&#xff1a; https://www.nowcoder.com/feed/main/det…

OpenAI的创始人World Coin项目介绍

&#x1f3af; 在一个崇高的目标支持下&#xff0c;不停地工作&#xff0c;即使慢&#xff0c;也一定会获得成功。—— 爱因斯坦 如果你对项目感兴趣请联系v&#xff1a;weixin605405145 一、项目速览 项目背景 Worldcoin由OpenAI的创始人Sam Altman于2019年创立&#xff0c;就…

【C++】的继承

继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构…

010、体系架构之TiFlash

TiFlash TiFlash 功能架构异步复制一致性读取场景选择是选择TiKV还是TiFLash TiFlash 功能 异步复制一致性读取(写虽然是异步&#xff0c;但读可以做到一致性)引擎智能选择计算加速 架构 TiFLASH 也是通过raft 算法进行同步&#xff0c;但它不怎么消耗资源&#xff0c;因为它…

ProGuard 进阶系列(二)配置解析

书接上文&#xff0c;从开源库中把代码下载到本地后&#xff0c;就可以在 IDE 中进行运行了。从 main 方法入手&#xff0c;可以看到 ProGuard 执行的第一步就是去解析参数。本文的内容主要分析源码中我们配置的规则解析的实现。 在上一篇文章末尾&#xff0c;在 IDE 中&#x…

Vue Router4

后端路由 客户端请求不同的URL服务器匹配URL并给一个Controller处理Controller处理完返回渲染好的HTML页面或数据给前端 优点&#xff1a; 不需要单独加载js和css&#xff0c;直角交给浏览器展示&#xff0c;有利于SEO优化 缺点&#xff1a; 页面有后端人员编写或由前端人员…

告别里程焦虑:深蓝S7超级增程打造超长续航

提起新能源汽车&#xff0c;估计许多人第一时间都会想要查看它的续航里程。 虽然如今的新能源汽车在续航里程上较过去已经有了很大改进&#xff0c;但是稀缺的充电桩和漫长的充电时间&#xff0c;仍然无法让需要长途出行的用户摆脱里程焦虑。 那么问题就来了&#xff1a;有没有…

基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码+文档

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码文档 视频演示 https://www.bilibili.com/video/BV1HW4y197Fe/ 系统介绍 摘 要 …

dubbo源码之-ExtensionInjector

dubbo源码之-ExtensionInjector 概述源码入口Extension 是如何获取到&#xff1f;SpiExtensionInjector 概述 其实ExtensionInjector 非常简单&#xff0c; 我们知道dubbo有ioc注入的功能&#xff0c; 是靠的set方法注入&#xff0c;对应的底层源码主要是ExtensionInjector 如…

MySQL数据库语言一、DDL

&#x1f618;作者简介&#xff1a;正在努力的99年打工人。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;动…

华为OD机试真题B卷 JavaScript 实现【分班】,附详细解题思路

一、题目描述 幼儿园两个班的小朋友在排队时混在了一起&#xff0c;每位小朋友都知道自己是否与前面一位小朋友是否同班&#xff0c;请你帮忙把同班的小朋友找出来。 小朋友的编号为整数&#xff0c;与前一位小朋友同班用Y表示&#xff0c;不同班用N表示。 二、输入描述 输…

uniapp/手机APP使用支付宝支付(服务端)

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

chatgpt赋能python:Python接口应用于SEO的指南

Python接口应用于SEO的指南 Python成为了web开发中最流行的语言之一&#xff0c;而且尤其在SEO领域中被广泛应用。一些Python库和框架可帮助SEO团队实现其目标&#xff0c;如排名跟踪&#xff0c;爬取数据&#xff0c;进行网站分析&#xff0c;等等。在本文中&#xff0c;我们…

基于Hexo和Butterfly创建个人技术博客,(9) 优化butterfly主题配置文章版本

Butterfly官方网站&#xff0c;请 点击进入 本章目标&#xff1a; 掌握butterfly主题对文章的配置&#xff0c;熟悉并可按需配置到个人的博客站点中&#xff0c;本章内容是一个必会章节&#xff0c;不仅包括文章的UI美化、SEO相关配置还包括其它增加的功能&#xff0c;内容不多…