Leetcode算法训练日记 | day34

devtools/2024/9/24 17:17:41/

专题九  贪心算法

一、K次取反后最大化的数组和

1.题目

Leetcode:第 1005 题

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。

重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

以这种方式修改数组后,返回数组 可能的最大和 。

示例 1:

输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

示例 2:

输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。

示例 3:

输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

2.解题思路

使用贪心算法解决K次取反后最大化的数组和问题。

largestSumAfterKNegations 函数中,我们首先使用 sort 函数和一个自定义的比较函数 cmp 对数组 nums 进行排序。这个比较函数 cmp 使用 abs 函数比较两个数的绝对值,确保绝对值较大的负数排在前面。接着,我们遍历数组,将前 k 个负数取反。如果 k 是奇数,且遍历结束后仍有剩余的 k,则将最后一个元素取反。最后,我们初始化一个变量 result 来存储最终的和,并遍历排序并取反后的数组 nums,将所有元素相加得到最终结果。这个方法利用了贪心算法的思想,即在每一步选择中都尝试达到最优化的结果,从而希望导致结果是全局最优的。

3.实现代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;class Solution {// 定义一个静态比较函数,用于自定义排序规则static bool cmp(int a, int b) {return abs(a) > abs(b); // 返回绝对值比较的结果,确保绝对值大的负数排在前面}
public:// largestSumAfterKNegations 函数用于计算最大约和int largestSumAfterKNegations(vector<int>& nums, int k) {sort(nums.begin(), nums.end(), cmp); // 使用自定义的比较函数对数组进行排序// 遍历数组for (int i = 0; i < nums.size(); i++) {if (nums[i] < 0 && k > 0) {// 如果当前元素是负数且 k 大于 0nums[i] = nums[i] * (-1);// 将当前元素取反,并减少 k 的值k--;}}// 如果 k 是奇数,最后一个元素取反if (k % 2 == 1) {nums[nums.size() - 1] *= -1;}int result = 0;// 初始化结果变量for (int a : nums) {result += a;// 遍历排序后的数组,计算所有元素的和}return result; // 返回计算得到的结果}
};//测试
int main()
{Solution p;vector<int> nums = { 4,2,3 };int k = 1;int result = p.largestSumAfterKNegations(nums,k);cout << "nums数组可能的最大和:" << result << endl;cout << endl;return 0;
}

二、加油站

1.题目

Leetcode:第 134 题

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
2.解题思路

(1)一般解法

(2)使用贪心算法解决加油站问题。

canCompleteCircuit 函数中,我们使用两个变量 curSumtotalSum 分别来记录当前窗口的剩余油量和整个数组的总剩余油量。同时,我们使用变量 start 来记录可能的环绕起始位置。循环遍历每个油量和消耗的位置,我们将当前位置的油量减去消耗,并累加到 curSumtotalSum 中。如果 curSum 小于 0,这意味着从当前起始位置开始的窗口内无法环绕一圈,因此我们将 start 移动到下一个位置,并将 curSum 重置为 0,以便开始计算新的窗口。在循环结束后,我们检查 totalSum,如果它小于 0,则表示在整个数组范围内无法环绕一圈,因此返回 -1。否则,我们返回 start 作为可能的环绕起始位置。这种方法利用了贪心算法的思想,通过维护一个滑动窗口的剩余油量来找到可能的环绕起始位置。

3.实现代码
#include <iostream>
#include <vector>
using namespace std;// 一、一般解法
class Solution1 {
public:// canCompleteCircuit 函数用于判断是否能够环绕一圈int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {// 遍历油量和消耗的对应位置for (int i = 0; i < cost.size(); i++) {  int rest = gas[i] - cost[i];// 计算当前位置的剩余油量int index = (i + 1) % cost.size();// 初始化下一个检查的位置为当前位置后的第一个位置// 模拟从当前位置 i 开始环绕行驶while (rest > 0 && index != i) {// 更新剩余油量,如果下一个位置的油量加上剩余油量大于等于消耗,则继续前进rest += gas[index] - cost[index];// 移动到下一个位置index = (index + 1) % cost.size();}// 如果从位置 i 开始能够环绕一圈回到起始位置,并且剩余油量非负,则返回起始位置if (rest >= 0 && index == i) return i;}return -1;// 如果没有找到可以环绕一圈的起始位置,返回 -1}
};// 二、贪心算法
class Solution2 {
public:// canCompleteCircuit 函数用于判断是否能够环绕一圈int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum = 0; // 初始化当前窗口的剩余油量int totalSum = 0; // 初始化整个数组的总剩余油量int start = 0; // 初始化可能的起始位置// 遍历油量和消耗的对应位置for (int i = 0; i < gas.size(); i++) {curSum += gas[i] - cost[i];// 将当前位置的油量和消耗相减,并加到当前窗口的剩余油量上totalSum += gas[i] - cost[i];// 将当前位置的油量和消耗相减,并加到总剩余油量上// 如果当前窗口的剩余油量小于 0,说明当前窗口内无法环绕一圈if (curSum < 0) {start = i + 1;// 重置起始位置为当前位置之后,即窗口滑动到下一个位置curSum = 0;// 重置当前窗口的剩余油量为 0}}// 如果整个数组的总剩余油量小于 0,说明整个数组内无法环绕一圈if (totalSum < 0) return -1;return start;// 返回可能的环绕起始位置}
};//测试
int main()
{Solution2 p;vector<int> gas = { 1,2,3,4,5 };vector<int> cost = { 3,4,5,1,2 };int result = p.canCompleteCircuit(gas, cost);cout << "可能的环绕起始位置:" << result << endl;cout << endl;return 0;
}

  

三、分发糖果

1.题目

Leetcode:第 135 题

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
2.解题思路

使用贪心算法解决分发糖果问题。

candy 函数中,我们首先创建了一个与 ratings 数组等长的 candyVec 数组,所有元素初始为 1,表示每个孩子至少得到 1 个糖果。

然后,我们进行两次遍历:

  1. 第一次从前向后遍历 ratings 数组,如果一个孩子的评分高于他左边的孩子,那么他的糖果数量应该比左边的孩子多一个。

  2. 第二次从后向前遍历 ratings 数组,如果一个孩子的评分高于他右边的孩子,那么他的糖果数量应该取右边孩子糖果数量加一和当前数量中的较大值。最后,我们遍历 candyVec 数组,将所有孩子的糖果数量累加起来,得到总共需要的糖果数量,并返回这个结果。这种方法利用了贪心算法的思想,通过维护一个糖果数组来动态地计算每个孩子应该得到的糖果数量。

3.实现代码
#include <iostream>
#include <vector>
using namespace std;class Solution {
public:// candy 函数用于计算分配糖果的最少数量int candy(vector<int>& ratings) {// 初始化一个与 ratings 大小相同的数组 candyVec,所有元素初始为 1// 这个数组将用于存储每个孩子应该得到的糖果数量vector<int> candyVec(ratings.size(), 1);// 从 ratings 数组的第二个元素开始向前遍历for (int i = 1; i < ratings.size(); i++) {if (ratings[i] > ratings[i - 1]) { // 如果当前孩子的评分高于他左边的孩子candyVec[i] = candyVec[i - 1] + 1;// 那么当前孩子的糖果数量应该比左边的孩子多一个}}// 从 ratings 数组的倒数第二个元素开始向前遍历for (int i = ratings.size() - 2; i >= 0; i--) {if (ratings[i] > ratings[i + 1]) {// 如果当前孩子的评分高于他右边的孩子candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);// 那么当前孩子的糖果数量应该取右边孩子糖果数量加一和当前数量中的较大值}}int result = 0;// 初始化结果变量 result,用于计算总共需要的糖果数量// 遍历 candyVec 数组,将所有孩子的糖果数量累加到 result 中for (int i = 0; i < candyVec.size(); i++) {result += candyVec[i];}return result;// 返回总共需要的糖果数量}
};//测试
int main()
{Solution p;vector<int> ratings = { 1, 0, 2 };int result = p.candy(ratings);cout << "总共需要的糖果数量:" << result << endl;cout << endl;return 0;
}

 ps:以上皆是本人在探索算法旅途中的浅薄见解,诚挚地希望得到各位的宝贵意见与悉心指导,若有不足或谬误之处,还请多多指教。


http://www.ppmy.cn/devtools/24969.html

相关文章

排序 + 模拟,LeetCode 1329. 将矩阵按对角线排序

一、题目 1、题目描述 矩阵对角线 是一条从矩阵最上面行或者最左侧列中的某个元素开始的对角线&#xff0c;沿右下方向一直到矩阵末尾的元素。例如&#xff0c;矩阵 mat 有 6 行 3 列&#xff0c;从 mat[2][0] 开始的 矩阵对角线 将会经过 mat[2][0]、mat[3][1] 和 mat[4][2] …

elementPlus treeselect相关问题

<el-tree-selectplaceholder"请选择业务代码":props"{ label: transactionName, value: transactionCode }"v-model"item.transactionCode"node-key"id":data"transactionList":default-expanded-keys"[item.transa…

前端可以掌握的nginx相关操作

一、前言&#xff1a; 在日常开发中&#xff0c;前端工程师可以把打好的前端包直接放到测试服务器上&#xff0c;自己再验证功能是否改好&#xff0c;这样可以提高开发效率&#xff0c;写篇笔记记录一下我个人用到的命令 二、使用的工具 用MobaXterm完成相关操作&#xff0c…

Rust Vec<T> 集合使用教程

Rust Vec 集合使用教程 文章目录 Rust Vec<T> 集合使用教程1. 创建和初始化 Vec<T>代码示例运行结果 2. 访问和修改 Vec<T> 中的元素代码示例运行结果 3. 添加和删除 Vec<T> 中的元素代码示例运行结果 4. 遍历 Vec<T>代码示例运行结果 5. 使用 V…

SRTP + RTCP + SCTP

SRTP&#xff08;Secure Real-time Transport Protocol&#xff09; 主要功能&#xff1a;SRTP 是 RTP 的一个扩展&#xff0c;提供额外的安全特性&#xff0c;如加密、完整性校验和认证。它旨在保护实时传输的音频和视频流不被窃听或篡改。加密传输&#xff1a;SRTP 使用强加密…

源码篇--Nacos服务--中章(8):Nacos服务端感知客户端实例变更-3

文章目录 前言一、客户端实例变更&#xff1a;二、实例变更感知&#xff1a;2.1 实例注册信息通知&#xff1a;2.1.1 接收DistroDataRequest 请求&#xff1a;2.1.2 onReceive 处理请求&#xff1a;2.1.3 processData 处理请求&#xff1a;2.1.4 handlerClientSyncData 处理数据…

《python编程从入门到实践》day16

昨日知识点回顾 从模块中导入类/模块 今日知识点学习 第十章 文件和异常 10.1 从文件中读取数据 10.1.1 读取整个文件 txt文件与程序文件在同一级目录 with open(pi_digits.txt) as file_object:contents file_object.read() print(contents)# 运行结果&#xff1a; # 3.1…

数据脱敏及数据库安全风险

数据脱敏是什么 数据脱敏&#xff0c;指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。这样就可以在开发、测试和其它非生产环境以及外包环境中安全地使用脱敏后的真实数据集。 在涉及客户安全数据或者一些商业的敏感数据的情况下&#…