算法打开13天

article/2025/8/1 7:21:23

41.前 K 个高频元素

(力扣347题)

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

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

解题思路

  1. 题目要求我们从一个整数数组 nums 中找出出现频率最高的 k 个元素。为了实现这一目标,我们可以采用以下步骤:
    1. 统计频率: 使用一个哈希表(unordered_map)来统计每个数字的出现次数。遍历数组 nums,对于每个数字,将其在哈希表中的计数加一。这样,我们可以快速得到每个数字的出现频率。
    2. 维护小顶堆: 使用一个优先队列(priority_queue)来维护频率最高的 k 个元素。优先队列的第一个元素是根节点,即堆顶元素。默认情况下,std::priority_queue 是一个大顶堆,但通过自定义比较函数 mycomparison,我们可以将其转换为小顶堆。小顶堆的特性是堆顶元素是所有元素中最小的,这正好符合我们的需求,因为我们希望在堆中保留频率最高的 k 个元素。当堆的大小超过 k 时,弹出堆顶元素(频率最小的元素),从而保证堆中始终存储的是频率最高的 k 个元素。
    3. 提取结果: 将小顶堆中的元素依次弹出,并存入结果数组。由于小顶堆的特性,弹出的顺序是频率从小到大,因此需要从后向前填充结果数组。最终,结果数组中存储的就是频率最高的 k 个元素。

单调队列与优先队列的区别

首先,我们需要明确 单调队列优先队列 是两种不同的数据结构,它们的行为和用途有所不同。

单调队列

  • 单调队列通常用于处理滑动窗口问题,它维护一个单调递增或单调递减的队列。
  • 单调队列的第一个元素通常是当前窗口的最小值或最大值。
  • 单调队列的实现通常基于双端队列(std::deque),而不是基于堆。

优先队列

  • 优先队列是一种基于堆的数据结构,通常用于维护一个动态集合,使得每次可以高效地访问和删除具有最高优先级的元素。
  • 优先队列的根节点(堆顶元素)是所有元素中优先级最高的元素。
  • 默认情况下,std::priority_queue 是一个大顶堆,即堆顶元素是所有元素中最大的。

大顶堆的定义

在大顶堆中:

  • 根节点(堆顶元素)是所有元素中最大的。
  • 对于任意节点 i,其子节点的值都小于或等于节点 i 的值。

代码

// 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。
// 你可以按 任意顺序 返回答案。
#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;// 使用单调队列(基于小根堆(底层逻辑是完全二叉树)
class Solution
{
public:// 小顶堆(自定义比较函数)class mycomparison{// 重载比较运算符 std::less<T> 把默认的大顶堆变成小顶堆public://  pair<int, int>是类模板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]]++;}// 定义一个小顶堆,用于存储频率最高的 k 个元素// std::priority_queue 默认是一个大顶堆priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que; // priority_queue优先队列// 遍历哈希表,将元素及其频率加入小顶堆for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++){// 将当前元素及其频率加入堆pri_que.push(*it);// 如果堆的大小超过 kif(pri_que.size() > k){pri_que.pop();}}// 将小顶堆中的元素依次弹出,存入结果数组vector<int> result(k);//    / 从后向前填充结果数组for(int i = k - 1;  i >= 0; i--){// 获取堆顶元素的数字部分result[i] = pri_que.top().first;// 弹出堆顶元素pri_que.pop();}// 返回结果数组return result;}
};
  • 时间复杂度: O(nlogk)
  • 空间复杂度: O(n)

42.二叉树的前序遍历

(力扣94题)

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

**输入:**root = [1,null,2,3]

输出:[1,2,3]

解释:

img

示例 2:

**输入:**root = [1,2,3,4,5,null,8,null,null,6,7,9]

输出:[1,2,4,5,6,7,3,8,9]

解释:

img

示例 3:

**输入:**root = []

输出:[]

示例 4:

**输入:**root = [1]

输出:[1]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

**进阶:**递归算法很简单,你可以通过迭代算法完成吗?

解题思路

前序遍历的顺序是“根-左-右”,即先访问根节点,然后递归遍历左子树,最后递归遍历右子树。实现前序遍历可以使用递归或**非递归(栈)**的方法。

递归方法

递归方法是最直观的实现方式,利用函数调用栈来实现遍历:

  1. 访问根节点:将当前节点的值加入结果向量。
  2. 递归左子树:对当前节点的左子树进行前序遍历。
  3. 递归右子树:对当前节点的右子树进行前序遍历。
  4. 终止条件:如果当前节点为空,直接返回。

递归方法的优点是代码简洁,但可能会受到递归深度的限制。

非递归方法(栈)

非递归方法使用显式的栈来模拟递归调用:

  1. 初始化栈:将根节点压入栈。
  2. 循环条件:当栈不为空时,执行以下操作:
    • 弹出栈顶节点,访问其值,并将其值加入结果向量。
    • 如果右子节点不为空,将其压入栈。
    • 如果左子节点不为空,将其压入栈。
  3. 终止条件:栈为空时,遍历完成。

非递归方法的优点是避免了递归调用的开销,适合处理较深的树结构

代码

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
struct TreeNode
{int val;TreeNode *left;TreeNode *right;TreeNode() : val(0), left(nullptr), right(nullptr) {}TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(nullptr), right(nullptr) {}
};class Solution
{
public:// 递归函数void traversak(TreeNode *cur, vector<int> &vec){if (cur == nullptr){return;}vec.push_back(cur->val);    // 当前节点traversak(cur->left, vec);  // 左traversak(cur->right, vec); // 右}// 前序遍历vector<int> preorderTraversal(TreeNode *root){vector<int> result;traversak(root, result);return result;}
};
class Solution
{
public:// 前序遍历vector<int> preorderTraversal(TreeNode *root){// 定义栈stack<TreeNode *> st;// 结果集vector<int> result;// 空节点 返回空if (root == NULL){return result;}// 根节点压入栈st.push(root);while (!st.empty()){TreeNode *node = st.top(); // 根节点st.pop();result.push_back(node->val);if (node->right){// 右(空节点不入栈st.push(node->right);}if (node->left){// 左(空节点不入栈st.push(node->left);}}return  result;}
};

return 只是结束当前栈帧的执行,并返回到调用它的函数。它不会影响其他栈帧的执行

43.二叉树的后序遍历

(力扣145题)

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

示例 1:

img

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

解题思路

后序遍历的顺序是“左-右-根”,即先递归遍历左子树,然后递归遍历右子树,最后访问根节点。实现后序遍历可以使用递归或**非递归(栈)**的方法。

递归方法

递归方法是最直观的实现方式,利用函数调用栈来实现遍历:

  1. 递归左子树:对当前节点的左子树进行后序遍历。
  2. 递归右子树:对当前节点的右子树进行后序遍历。
  3. 访问根节点:将当前节点的值加入结果向量。
  4. 终止条件:如果当前节点为空,直接返回。

递归方法的优点是代码简洁,但可能会受到递归深度的限制。

非递归方法(栈)

非递归方法使用显式的栈来模拟递归调用:

  1. 初始化栈:将根节点压入栈。
  2. 循环条件:当栈不为空时,执行以下操作:
    • 弹出栈顶节点,访问其值,并将其值加入结果向量。
    • 如果左子节点不为空,将其压入栈。
    • 如果右子节点不为空,将其压入栈。
  3. 反转结果集:由于栈的后进先出特性,最终结果需要反转。
  4. 终止条件:栈为空时,遍历完成。

非递归方法的优点是避免了递归调用的开销,适合处理较深的树结构。

代码

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
struct TreeNode
{int val;TreeNode *left;TreeNode *right;TreeNode() : val(0), left(nullptr), right(nullptr) {}TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(nullptr), right(nullptr) {}
};class Solution
{
public:// 递归函数void traversak(TreeNode *cur, vector<int> &vec){if (cur == nullptr){return;}traversak(cur->left, vec);  // 左traversak(cur->right, vec); // 右vec.push_back(cur->val);    // 当前节点}// 后序遍历vector<int> postorderTraversal(TreeNode *root){vector<int> result;traversak(root, result);return result;}
};class Solution
{
public:// 后序遍历vector<int> postorderTraversal(TreeNode *root){// 定义栈stack<TreeNode *> st;// 结果集vector<int> result;// 空节点 返回空if (root == NULL){return result;}// 根节点压入栈st.push(root);while (!st.empty()){TreeNode *node = st.top(); // 根节点st.pop();result.push_back(node->val);if (node->left){// 左(空节点不入栈st.push(node->left);}if (node->right){// 右(空节点不入栈st.push(node->right);}}// 反转结果集reverse(result.begin(), result.end());return  result;}
};

http://www.hkcw.cn/article/QrBKbgWXZh.shtml

相关文章

【Centos7】最小化安装版本安装docker(无wget命令避坑)

文章目录 Centos7安卓docker1. 检查CentOS内核版本2. 一键将CentOs的yum源更换为国内阿里yum源3. 使用root权限登录CentOS。确保yum包更新到最新4.安装docker5.Docker阿里云镜像加速器 Centos7安卓docker 1. 检查CentOS内核版本 Docker要求CentOS系统的内核版本高于3.10&…

Vue-1-前端框架Vue基础入门之一

文章目录 1 Vue简介1.1 Vue的特性1.2 Vue的版本 2 Vue的基础应用2.1 Vue3的下载2.2 Vue3的新语法2.3 vue-devtools调试工具 3 Vue的指令3.1 内容渲染指令{{}}3.2 属性绑定指令v-bind3.3 事件绑定指令v-on3.4 双向绑定指令v-model3.5 条件渲染指令v-if3.6 列表渲染指令v-for 4 参…

Lighttpd CGI配置:404错误排查实录

目录 引言 编写测试程序 前端代码 后端代码 配置CGI模块&#xff08;mod_cgi&#xff09; 如何检查404错误 测试结果 ​编辑 结语 引言 在前面的测试中&#xff0c;我们将lighttpd移植到x210开发板中&#xff0c;今天学生报告说她在进行CGI程序测试时总是遭遇404错误…

卢昌海 | 质量的起源

注&#xff1a;本文为卢昌海 | 质量的起源五篇合辑。 公式巨多&#xff0c;未一一校排。 如有内容异常&#xff0c;请看原文。 卢昌海 | 质量的起源 &#xff08;一&#xff09; 一、引言 物理学是一门试图在最基本层次上理解自然的古老科学&#xff0c;其早期曾是哲学的一部…

5、设置时区、链接wifi

一、修改时区&#xff1a; 输入以下命名打开raspbian系统的设置界面 sudo raspi-config 如下图&#xff0c;通过键盘上下键&#xff0c;移动到第 5 步“localisation Options”&#xff0c;回车进入。 注:每个系统版本不一样&#xff0c;选择就不一样&#xff0c;我的是在第…

81、使用DTU控制水下灯光控制

基本思想:记录调试济南有人DTU控制水下灯光控制 一、首先连接dtu设备,进行供电模块的链接和RS-485控制水下探照灯 线头链接方方式示意图,供电线接入之后,要保证设备处于工作状态,如果设备在供电不处于工作状态,那可能火线和零线接反了,请重新接入; 将红色的线接入RS-4…

【js逆向】易车网某车辆对比信息X-sign

目标网址&#xff1a;aHR0cHM6Ly9jYXIueWljaGUuY29tL2JpeWFkaWUyL3BlaXpoaS8 f12刷新网页查看数据接口 断点调试&#xff1a; 我们的目标网址是 param/get_param_details, 用条件断点 e.url.includes(param/get_param/details) 向上跟栈&#xff0c;这里X-Sign已经生成&#x…

基于TMC5160堵转检测技术的夹紧力控制系统设计与实现

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 90万阅读 1.6万收藏 一、技术背景与系统原理 在工业自动化领域&#xff0c;夹紧力控制是精密装配、机床夹具等场景的核心需求。传统方案多采用压力传感器伺服电机的闭环控制方式&#xff0c;但存在系统复杂…

青岛红狮主教练马永康下课 球队保级压力增大

北京时间5月31日晚,2025赛季中甲第11轮多场比赛展开,广西平果在主场迎战青岛红狮。比赛前,两队分别位于中甲积分榜的倒数第一和第二位。上半场马特乌斯为广西平果打破僵局,下半场双方均未能改写比分。最终,广西平果以1-0战胜青岛红狮,取得联赛首胜并保持了两轮不败,而青…

Maven(黑马)

Maven 是一个强大的项目管理和构建自动化工具&#xff0c;主要用于 Java 项目的构建、依赖管理和文档生成。它通过使用 POM&#xff08;Project Object Model&#xff09;文件来管理项目的配置和依赖关系&#xff0c;从而实现项目的自动化构建和管理。以下是 Maven 的一些核心概…

项目练习:element ui 的icon放在button的右侧

文章目录 一、需求描述二、左侧实现三、右侧实现 一、需求描述 我们知道&#xff0c;element ui的button一般都会配置一个icon 这个icon默认是放在左侧的。 如何让它放在右侧了&#xff1f; 二、左侧实现 <el-buttontype"primary"plainicon"el-icon-d-arr…

大连一景区日撒1000斤蚬子 吸引游客赶海乐

近两日,多名网友分享了在辽宁省大连市夏家河子海滨浴场偶遇工作人员开着铲车、三轮车给游客撒蚬子赶海的情景。景区回应称,在沙滩上撒蚬子是为了让赶海的游客都能挖到东西。这两天,景区每天需要撒约1000斤的蚬子。此外,还有巴掌大的鲍鱼和海螺,如果游客捡到可以兑换礼品。…

位运算 #常见位运算总结 #题解

系列文章目录 leetcode - 双指针问题_leetcode双指针题目-CSDN博客 leetcode - 滑动窗口问题集_leetcode 滑动窗口-CSDN博客 高效掌握二分查找&#xff1a;从基础到进阶-CSDN博客 leetcode - 前缀和_前缀和的题目-CSDN博客 动态规划 - 斐波那契数列模型-CSDN博客 目录 系…

openpnp - 给M4x0.7mm的直油嘴加油的工具选择

文章目录 openpnp - 给M4x0.7mm的直油嘴加油的工具选择概述如果换上带卡口的M4x0.7直油嘴END openpnp - 给M4x0.7mm的直油嘴加油的工具选择 概述 X导轨用了一个HG15的滑块 滑块上的注油口的黄油嘴是M4x0.7mm的直油嘴。 外表面是6边形的柱子&#xff0c;没有可以卡住加油嘴工…

SSL/TLS 协议详解:安全通信的基石

一、概述 SSL&#xff08;Secure Sockets Layer&#xff09; 及其继任者 TLS&#xff08;Transport Layer Security&#xff09; 是位于 传输层&#xff08;TCP&#xff09;与应用层之间 的加密协议&#xff0c;用于在网络通信中实现 机密性、身份认证和数据完整性。 核心目标…

象棋里的卧槽马、侧面虎、金钩马的方位与解析

在中国象棋里&#xff0c;根据马的方位&#xff0c;有不同的称谓&#xff0c;比如卧槽马、侧面虎、金钩马&#xff1b;车也是一样&#xff0c;比如有肋车、沉底车、相位车等。     按照《象棋攻防与口诀》的"边炮车砍象&#xff0c;三七马肋车"口诀&#xff0c;这里…

内存管理 : 05 内存换入-请求调页

操作系统内存换入 - 请求调页讲解 这一讲主要内容是内存的换入&#xff0c;下一讲要讲内存的换出&#xff08;swap out&#xff09;&#xff0c;这两讲合在一起就是内存的换入换出。讲完内存的换入换出&#xff0c;操作系统关于内存管理这部分内容&#xff0c;也就是我们课程里…

任务23:创建天气信息大屏Django项目

任务描述 知识点&#xff1a; Django 重 点&#xff1a; Django创建项目Django视图函数Django路由Django静态文件Django渲染模板 内 容&#xff1a; 使用PyCharm创建大屏项目渲染大屏主页 任务指导 1. 使用PyCharm创建大屏项目。 创建weather项目配置虚拟环境创建ch…

回溯算法!!

只要有递归就会有回溯&#xff0c;回溯隐藏在递归函数的下面 回溯算法是回溯搜索算法&#xff0c;是纯暴力的搜索算法 一般用于解决以下问题 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合&#xff0c;组合是不强调元素顺序的&#xff0c;排列是强调元素顺序。切…

算法学习--持续更新

算法 2025年5月24日 完成&#xff1a;快速排序、快速排序基数优化、尾递归优化 快排 public class QuickSort {public void sort(int[] nums, int left, int right) {if(left>right){return;}int partiton quickSort(nums,left,right);sort(nums,left,partiton-1);sort(nu…