【C++ 算法进阶】算法提升二

news2024/10/23 5:32:29

算法提升二

  • 最大分配工资问题 (贪心)
    • 题目
    • 分析
    • 代码详解
  • 数组有序问题 (贪心)
    • 题目
    • 分析
    • 代码详解
  • 消息流问题 (数据结构设计)
    • 题目
    • 分析
    • 代码详解
  • 可乐问题 (Coding能力)
    • 题目
    • 分析
    • 代码详解
  • 司机问题(动态规划)
    • 题目
    • 分析
    • 代码详解

最大分配工资问题 (贪心)

题目

给定数组Hard和Money 长度都为N

Hard[i]表示i号的难度 Money[i]表示i号工作的收入

给定数组ability 长度都为M ability [i] 表示i号人的工作能力

没一号工作都可以提供无数的岗位 难度和收入都一样 但是人的能力必须大于等于这份工作的难度 才可以上班

现在要求返回一个长度为M的数组ans ans[i]表示i号能获得的最好收入

分析

对于这道题目来说 最简单能想到的方法就是遍历整个难度收入数组 就可以找到最高的收入

如果我们想要获得最好的收入我们就需要想到这样的一个贪心点

难度 - 收入 必须要呈正相关 不然一个难度更高收入更低的工作谁会去做呢

有了这个大前提之后我们便可以将难度收入数组排序

  • 同等难度下 我们只要收入最高的
  • 难度越高 收入必须越高

这样子我们就能得到一个数组(当然 为了比较方便 我们也可以使用map来存储)

之后只需要使用二分查找 或者使用map的upper_bond函数就可以找到每号人的最好收入了

代码详解

bool Compare(pair<int, int>& p1, pair<int, int>& p2) {
	if (p1.first < p2.first)
	{
		return true ; 
	} else if (p1.first > p2.first) {
		return false;
	} else
	{
		return p1.second > p2.second;
	}
}
	sort(vJob.begin(), vJob.end(), Compare);

我们首先写出一个比较函数来job进行排序 (难度小的工作排在难度大的工作前面 同等难度的工作收入高的排在前面)

	// 之后我们创建一个map 开始遍历整个vJob数组 从最低难度开始往后选 我们排除掉难度增加了但是工资没有增加的
	map<int, int> mapJob = {};
	mapJob[vJob[0].first] = vJob[0].second;
	int Pre = vJob[0].first; // pre表示上一份工作的难度
	for (auto x : vJob)
	{
		if (Pre != x.first && mapJob[Pre] < x.second)
		{
			mapJob[x.first] = x.second;
			Pre = x.first; // pre表示上一份工作的难度
		}
	}

之后我们创建一个map有序表 开始遍历整个job数组 从最低难度开始往后 按照同等难度下只要收入最高的 难度增加收入必须增加的原则来插入map

之后我们通过map中的 upper_bound 函数便可以解决该问题

upper_bound(keyi)函数详解

该函数为map的内置函数 使用它可以找到距离keyi最近 并且大于keyi的一个迭代器

	for (auto abliity : vAbility) {
		auto it = mapJob.upper_bound(abliity);

		if (it == mapJob.begin())
		{
			// 0
		}
		else 
		{
			// 表示是最大值 it -1 即可
		}
	}

所以说 我们可以直接使用上面的函数来求出最终答案

数组有序问题 (贪心)

题目

给定一个数组arr 只能对arr中的一个子数组排序 但是想让整个arr抖有序 返回满足这一设定的子数组中 最短的是多长

分析

思路分析

要解决这个问题其实很简单 我们只需要将整个数组排序 然后一一对比其中不一样的值 之后在找到这些值的左右区间即可 这样子做的时间复杂度为N * LogN

比如下图

在这里插入图片描述

当然 这样子并不是最完美的解法 但是应付笔试应该是够用的 因为LogN 肯定是一个很小的数值 在时间复杂度上接近于N

这道题最完美的解法是时间复杂度为N的解法 具体思路如下

在这里插入图片描述

我们首先从左边第一个数字开始 按顺序往右推 如果下一个数字小于我们当前指针指向的数字我们就画上 X 反之则画上 √ 接着指针右移 之后我们找到最后一个打X的位置

在这里插入图片描述

之后我们从右边第一个数字开始 按顺序往左推 如果下一个数字大于我们的当前指针指向的数字 我们就画上X 反之则画上 √ 接着指针左移 之后我们找到最后一个打X的位置

最后的结果差不多是这样 (这里以红色圆圈代表X号)

在这里插入图片描述
我们观察可以发现 这两个位置圈起来的子数组排序下 刚好就是我们需要的答案

原理 (默认顺序)

因为顺序数组一定有前面的数字小于等于后面的数字 后面的数字大于等于前面的数字

所以说对于一个不是顺序结构的数组 我们只需要通过上面的方法找到这两个边界便可以将其变有序了

代码详解

int FindFormLeft(vector<int>& v1) {
	if (v1.size() == 1)
	{
		return 0;
	}

	int lnKey = 0;

	for (int i = 0; i < v1.size() - 1; i++)	{
		if (v1[i] > v1[i + 1]) {
			lnKey = i + 1;
		}
	}

	return lnKey;
}

从左边开始找的代码如上 接下来只需要写出从右边开始找的代码 之后将他们两个的返回值捕捉即可

消息流问题 (数据结构设计)

题目

已知一个消息流会不断地吐出整数1~N 但不一定按照顺序依次吐出
如果上次打印的序号为i 那么当i+1出现的时候 请打印i+1及其之后接收过的并且连续的所有数
直到1~N全部接收并打印完
请设计出这种数据结构

分析

这其实是一个经典的网络流设计题

我们都知道 网络发动数据肯定不可能全部按顺序到达是吧

比如说我们这里发送一串数据 1 2 3 4

对面接收到的时候可能就是 4 1 3 2

但是我们都知道 数据的顺序是很重要的 所以说我们希望对面接收之后先观察下顺序有没有乱再打印

解法分析

我们要打印的是一个连续的区间 所以说等数据到了之后让其跟我们现在需要的数据对比的方式肯定不可行

那我们想想看 怎么才能让一段数据连续呢? ---- 单链表

我们可以使用单链表来表示一串连续的数据 但是这里也会出现一个问题

我们怎么保证接收到的数据正好让这一串单链表连接起来呢?

这里提供一种思路

我们创建两章哈希表 头表和尾表

头表 存储一串连续的单链表的头部
尾表 存储一串连续的单链表的尾部

图中的数字代表一个节点

图中的每个数字都代表一个节点 后面其实都有一个指针指向空

假如说现在加入一个四号节点

在这里插入图片描述

那么图片就会变成上面这样
在这里插入图片描述

但是由于3 4 5本身就是一串连续的字节流 所以说头表尾表就会变成下面的形式

在这里插入图片描述

头表中只剩下一个3 尾表中只剩下一个5

假设我们现在想要的第一个数字是2 那么当2来到之后只需要和3 头尾相连 我们打印整个单链表就能得到我们想要的结果了 (当然我们要记得处理下头尾表数据)

代码详解

我们首先设计一个信息流节点

struct Node
{
	int val; 代表节点的编号 我们使用该编号来排序
	char c; 代表节点传输的信息 
	Node* next; next指针
};
map<int, Node*> mapHead = {};
map<int, Node*> mapTail = {};
int WantNum = 1; // 我们现在想要的数字

接下来设计下头尾表和我们想要的数字

注意!!!! 在C++中的map和Java中的map不一样 如果我们定义map<int, Node> mapHead 那么穿进去的Node只会是一个拷贝值 他俩的指针没有任何关系 但是在Java中 他们就是一种引用关系
所以说在C++中我们要使用指针

接下来完整代码如下

// 函数:将节点放入链表
void PutNode(Node& n1) {
    // 首先将节点插入到头表和尾表
    mapHead[n1.val] = &n1; // 使用指针
    mapTail[n1.val] = &n1; // 使用指针

    // 连接前驱节点
    if (mapTail.count(n1.val - 1) == 1) {
        mapTail[n1.val - 1]->next = &n1; // 连接前驱
        mapTail.erase(n1.val - 1); // 删除前驱的记录
        mapHead.erase(n1.val);
    }

    // 连接后继节点
    if (mapHead.count(n1.val + 1) == 1) {
        n1.next = mapHead[n1.val + 1]; // 连接后继
        mapHead.erase(n1.val + 1); // 删除后继的记录
        mapTail.erase(n1.val);
    }

    // 如果当前节点是我们想要的数字
    if (n1.val == WantNum) {
        Node* cur = &n1; // 当前节点
        while (cur) {
            cout << cur->val << " "; // 打印当前节点的值
            cur = cur->next; // 移动到下一个节点
            WantNum++; // 更新想要的数字
        }
        cout << endl;

        // 清除尾表中最后一个节点的记录
        mapTail.erase(n1.val); // 根据需要清理
    }
}

大家可以自己生成一些测试用例来验证下代码的正确性

可乐问题 (Coding能力)

题目

购买机只支持硬币支付 且收 退只支持10 50 100三种面额 一次购买只能出一瓶可乐 且投钱和找零都遵循优先使用大钱的原则
需要购买的可乐数目是m
其中手头拥有的10 50 100的数量分别为a b c
可乐的价格为X (x是10的整数倍)
请计算出需要投入硬币次数

分析

这道题目其实就是纯粹考验coding能力 也就是我们如何将一个实际问题转化为工程问题的能力

我们对问题进行分析

首先让我们用大面值的面额的纸币去购买一瓶可乐

这里就出现了三种情况

  1. 比当前面值大的纸币还没有用完 需要用当前纸币补上零钱
  2. 当前纸币足够买x瓶可乐 这里就要考虑找零问题
  3. 当前纸币没有了或者说剩下的额度不足以购买一瓶可乐了 此时我们就要考虑使用下个额度的纸币

转化为代码风格如下
1.

	// 1. 如果前面有剩下的零钱我们要把他用上
	int lnLastMoney = 0;
	int lnLastZhang = 0;
// 使用公式进行计算即可
	lnLastMoney += // 剩下的总钱数
	lnLastZhang += // 剩下的总张数

代码详解

整体代码表示如下

// 定义初始硬币数量
int a = 20;  
int b = 20; 
int c = 1;  
const int ColaMoney = 30;  // 每瓶可乐的价格
int x = 5;  // 需要购买的可乐数量

// 找零函数
void Change(vector<int>& vMoney, vector<int>& varr, int lnChange) {
    for (int i = 0; i < 3; i++) {
        while (lnChange >= vMoney[i] && varr[i] > 0) {
            lnChange -= vMoney[i];
            varr[i]--;  // 减少相应面额的硬币数量
        }
    }
}

// 购买可乐函数
int BuyCola() {
    int lnLastMoney = 0;  // 上次剩余的钱
    int lnLastZhang = 0;  // 上次剩余的张数
    int lnTotalCoins = 0;  // 总的投币次数

    // 面额分别是100, 50, 10
    vector<int> vMoney = { 100, 50, 10 };
    // 对应的硬币数量
    vector<int> varr = { c, b, a };

    while (x > 0) {
        for (int i = 0; i < 3; i++) {
            if (x <= 0) break;

            // 如果有上次剩下的钱
            if (lnLastZhang != 0 && lnLastMoney != 0) {
                int lnNeedZhang = ((ColaMoney - lnLastMoney) + vMoney[i] - 1) / vMoney[i];  // 需要的张数,向上取整

                // 如果硬币数量不够
                if (lnNeedZhang > varr[i]) {
                    lnLastMoney += varr[i] * vMoney[i];
                    lnLastZhang += varr[i];
                }
                else {
                    lnTotalCoins += (lnNeedZhang + lnLastZhang);
                    x--;  // 买掉一瓶可乐
                    varr[i] -= lnNeedZhang;
                    Change(vMoney, varr, lnLastMoney);  // 进行找零
                    lnLastMoney = 0;  // 重置剩余钱数
                    lnLastZhang = 0;  // 重置剩余张数
                }
            }

            // 正常购买可乐的情况
            int lnBuyCount = (vMoney[i] * varr[i]) / ColaMoney;  // 当前硬币总额能买的可乐数量
            int lnLastCoinMoney = (vMoney[i] * varr[i]) % ColaMoney;  // 剩余的金额

            if (lnBuyCount == 0) {
                // 如果不能买一瓶,则累积剩余钱
                lnLastMoney += varr[i] * vMoney[i];
                lnLastZhang += varr[i];
            }
            else if (x <= lnBuyCount) {
                // 如果买够了
                lnTotalCoins += (x * ColaMoney + vMoney[i] - 1) / vMoney[i];
                x = 0;
            }
            else {
                // 钱花完了但不够买下全部可乐
                if (lnLastCoinMoney % vMoney[i] == 0) {
                    lnTotalCoins += (vMoney[i] - (lnLastCoinMoney / vMoney[i]));
                    x -= lnBuyCount;  // 购买的可乐数量
                    lnLastMoney = lnLastCoinMoney;  // 更新剩余金额
                    lnLastZhang = varr[i];
                }
            }
        }
    }

    return lnTotalCoins;
}

int main() {
    int result = BuyCola();
    cout << "总投币次数为: " << result << endl;
    return 0;
}

司机问题(动态规划)

题目

现在有司机N*2人 调度中心会将所有的司机平均分配给A B 两个区域
他们每个人去A B区域会获得不同的工资 (给定两个数组A B来表示他们可能的收入)
现在要求你所有的调度方案中能让所有司机总收入最高的方法

分析

如果之前看过我写的动态规划部分博客的同学看到这个问题就应该联想到从左到右的递归模型

我们只需要从左到右 列举出每一种可能性 这个问题就能迎刃而解了

具体思路如下

我们定义一个函数 process(index , rest)

其中index表示当前司机下表

rest表示A剩余能够分配的司机数(因为强制要求司机必须要一半一半)

所以说我们就能得到两种可能性

  1. 司机去A 之后加上 ``process(index+ 1, rest - 1)
  2. 司机去B 之后加上 ``process(index+ 1, rest )

之后只要不断地递归就可以了

代码详解

当然我们最后可以使用一个dp数组来完成记忆化搜索让该函数的时间复杂度尽可能的降低

int process(vector<int>& A, vector<int>& B, int index, int rest , vector<vector<int>>& dp) {
	if (index == A.size())
	{
		return 0;
	}
	if (rest == 0)
	{
		return B[index] + process(A, B, index + 1, rest ,dp);
	}

	if (index + rest > A.size())
	{
		return A[index] + process(A, B, index + 1, rest , dp);
	}
	// 检查缓存
	if (dp[index][rest] != -1) {
		return dp[index][rest]; // 如果已有结果,则直接返回
	}

	// 选择去B区和去A区的最大收入
	int p1 = process(A, B, index + 1, rest, dp) + B[index]; // 司机去B区
	int p2 = process(A, B, index + 1, rest - 1, dp) + A[index]; // 司机去A区

	// 记录并返回最大收入
	dp[index][rest] = max(p1, p2);
	return dp[index][rest];
}

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

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

相关文章

YOLOv9下载安装运行

1、进入GitHub的YOLOv9官网 https://github.com/WongKinYiu/yolov92、clone或下载项目 https://github.com/WongKinYiu/yolov9.githttps://codeload.github.com/WongKinYiu/yolov9/zip/refs/heads/main2.1、进入控制台下载项目 git clone https://github.com/WongKinYiu/yol…

在线培训知识库+帮助中心:教育行业智慧学习的创新桥梁

在数字化转型的浪潮中&#xff0c;教育行业正经历着前所未有的变革。为了应对日益增长的学习需求&#xff0c;提升教育质量&#xff0c;构建一个集在线培训知识库与帮助中心于一体的智慧学习环境&#xff0c;已成为教育行业转型升级的重要方向。这一创新模式不仅优化了学习资源…

无人机飞手执照培训费用较高原因分析

无人机飞手执照培训费用较高的原因可以归结为多个方面&#xff0c;以下是对这些原因的具体分析&#xff1a; 一、课程内容的全面性和专业性 无人机飞手执照培训涵盖了从无人机基础知识到高级飞行技巧、从组装调试到故障维修的多个方面。这种全面性和专业性要求培训机构提供高…

猎板PCB测试大讲堂:让你测试的明明白白

在电子研发领域&#xff0c;PCB&#xff08;印刷电路板&#xff09;的检测是确保产品质量的关键环节。主要的检测方式包括飞针测试和测试架测试。以下是这两种技术的详细介绍&#xff0c;旨在为电子研发工程师提供技术资料。 PCB飞针测试&#xff08;Flying Probe Test&#x…

麦克风哪个品牌音质最好,无线领夹麦克风十大品牌推荐

随着科技的进步&#xff0c;无线领夹麦克风的技术也在不断革新。从传统的模拟信号传输到如今的数字信号传输&#xff0c;再到智能降噪、自适应增益控制等先进技术的应用&#xff0c;无线领夹麦克风的录音品质得到了显著提升。然而&#xff0c;市场上仍有一些产品采用过时的技术…

vue2使用pdfjs-dist实现pdf预览(iframe形式,不修改pdfjs原来的ui和控件)

前情提要 在一开始要使用pdf预览的时候&#xff0c;第一次选的是vue-pdf&#xff0c;但是vue-pdf支持的功能太少&#xff0c;缺少了项目中需要的一项-复制粘贴功能 之后我一顿搜搜搜&#xff0c;最终貌似只有pdfjs能用 但是网上支持text-layer的貌似都是用的2.09那个版本。 使…

嵌入式AI博客目录

文章目录 环境搭建ubuntu下载安装c版opencv4.7.0和4.5.0 & 安装opencv4.5.0报错及解决方法ubuntu系统vscode配置c版opencv & 编译运行c播放视频代码&#xff08;包含&#xff1a;vscode使用copencv&#xff0c;创建CmakeList.txt&#xff0c;创建编译项目&#xff09;u…

【干货】2024期中考试成绩公布方式

本学期的期中考试即将拉开帷幕&#xff0c;而考试后的成绩发布往往是老师觉得最复杂耗时的工作。老师完全可以抛弃这种费力耗时的“笨方法”&#xff0c;只需要易查分&#xff0c;即可一分钟完成成绩发布的工作。教师发布省心&#xff0c;家长查询安心。 易查分是一个在线的查询…

智能健康推荐:SpringBoot技术应用

5系统详细实现 5.1 管理员模块的实现 5.1.1 用户管理 基于智能推荐的卫生健康系统的系统管理员可以管理用户管理&#xff0c;可以对用户管理信息添加修改删除以及查询操作。具体界面的展示如图5.1所示。 图5.1 用户管理信息管理界面 5.1.2 科室类型管理 系统管理员可以查看对…

让你的MacOS剪切板变得更加强大,如何解决复制内容覆盖的问题

MacOS的日常使用过程中&#xff0c;肯定少不了复制粘贴&#xff0c;不论是文本内容还是文件&#xff0c;复制粘贴是避不开的操作&#xff0c;如果需要复制粘贴的内容不多&#xff0c;那么普通的复制粘贴就可以完成了&#xff0c;但是当有同样的内容需要输入不同的地方的时候&am…

SAM应用:医学图像和视频中的任何内容分割中的基准测试与部署

医学图像和视频中的任何内容分割&#xff1a;基准测试与部署 目录 摘要&#xff1a;一、引言1.1 SAM2 在医学图像和视频中的应用 二.结果2.1 数据集和评估协议2.2 二维图像分割的评估结果 三 讨论四 局限性和未来的工作五、方法5.1数据来源和预处理5.2 微调协议5.3 评估指标 总…

flask项目创建、flask使用、python使用flask、pycharm创建flask项目

创建项目前python和pycharm要装好 打开pycharm创建项目&#xff1a; 虚拟空间下载flask: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Flask 下载好后&#xff1a;下载扩展 pip install Flask-SQLAlchemy -i https://mirrors.aliyun.com/pypi/simple/ 目录结构&…

结合空口分析BLE AUDIO之PAC

PAC&#xff0c;published audio capability用于声明audio服务能力&#xff0c;是BLE AUDIO核心服务之一&#xff0c;下面以手机和耳机为例&#xff0c;结合空口分析PAC到底有哪些交互内容&#xff1a; 1&#xff1a;读取Source/Sink PAC 首先手机会读取左耳的Source PAC和Si…

创客项目秀 | 基于XIAO ESP32S3 Sense 的最小 DIY 相机

作者&#xff1a;Md. Khairul Alam 故事背景 我一直对间谍小工具和微型电子产品非常着迷。我一直想创造一个可以装在口袋里的微型相机&#xff0c;能够悄无声息地捕捉精彩瞬间。随着技术的进步和像 Xiao ESP32S3 Sense 这样功能强大的微控制器的出现&#xff0c;我终于有机会实…

自然语言处理:第五十三章 Ollama

代码&#xff1a; ollama/ollama: Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models. (github.com) 官网&#xff1a; Ollama 写在前面: 笔者更新不易&#xff0c;希望走过路过点个关注和赞&#xff0c;笔芯!!! 写在前面: 笔者更新不易…

Linux YUM设置仓库优先级

1.安装yum-plugin-priorities优先级插件 yum install yum-plugin-priorities -y 2.设置仓库优先级 vim /etc/yum.repos.d/local.repo [local] namecentos7.5 baseurlfile:///mnt enable1 gpgcheck0 priority1 注释&#xff1a; priority1 #数字越小代表优先级越高&#xff…

测试用例评审流程优化

测试用例评审是QA日常工作流程中的关键一环&#xff0c;是QA同学完善测试用例、交流测试经验的好机会。 负责组内测试用例建设以来&#xff0c;作者对于评审流程做了一些优化工作。本文作者将整个优化过程中的心得体会做了一个总结&#xff0c;希望能给大家带来帮助。 01 原始流…

rom定制系列------小米6x_MIUI14_安卓13刷机包修改写入以及功能定制 界面预览

在接待一些定制化系统中。有很多工作室或者一些特殊行业的友友需要在已有固件基础上简略修改其中的功能。方便使用。例如usb调试默认开启。usb安装设置以及usb安装与内置删减一些app的定制服务。今天给友友预览其中小米6X此款机型定制相关的一些界面与功能演示。 定制机型以及…

多线程基本知识

目录 程序&#xff0c;进程&#xff0c;线程 程序 含义 我的理解&#xff1a; 举例 进程 含义 我的理解&#xff1a; 举例 线程 含义 我的理解&#xff1a; 举例 多线程的并发 并发的含义 并发和并行的区别 并发含义 区别 线程的创建 继承 Thread 类创建多线…

缺失d3dcompiler43.dll如何修复?总结5种简单方法

d3dcompiler_43.dll是Microsoft DirectX的一个关键组件&#xff0c;对于图形渲染和多媒体应用至关重要。DirectX是由微软开发的一套多媒体编程接口&#xff0c;它提供了硬件加速的图形和声音功能&#xff0c;以及其他与多媒体和游戏相关的功能。d3dcompiler_43.dll在DirectX 11…