当前位置: 首页 > news >正文

每日算法刷题Day64:8.24:leetcode 堆6道题,用时2h30min

五、反悔堆

1.套路

1.基于堆的反悔贪心。
2.堆中存产生坏影响的元素,接着贪心的选择元素,并将坏元素存入堆,若不满足条件,则从堆中选出最坏的元素进行反悔,直至满足条件。

2.题目描述

1.小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。
小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。

3.学习经验
1. LCP 30. 魔塔游戏(中等)

LCP 30. 魔塔游戏 - 力扣(LeetCode)

思想

1.小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。
小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。
2.堆中存负数房间,贪心的顺序遍历,直至血量为非正,再从堆中取出扣血数最多的房间(负数最小,小顶堆),进行反悔(此题影响是血量上升,调整次序加1,无需显示放到末尾)

代码
class Solution {
public:typedef long long ll;int magicTower(vector<int>& nums) {int n = nums.size();ll blood = 1;ll sum = 0;for (int& x : nums)sum += x;if (blood + sum <= 0)return -1;priority_queue<int, vector<int>, greater<int>> pq;int res = 0;for (int i = 0; i < n; ++i) {blood += nums[i];if (nums[i] < 0)pq.push(nums[i]);while (blood <= 0) {int tmp = pq.top();pq.pop();blood -= tmp;++res;}}return res;}
};

六、懒删除堆

1.套路

1.支持删除堆中任意元素。(普通堆只支持删除堆顶)
2.核心思想:不立即删除目标元素,而是记录下来(可以记录现在的,也可以记录删除的)等到它冒泡到堆顶时再真正删除。(理解核心思想最重要)
代码实现:用哈希表记录要删除的元素及其次数(有重复)以及堆实际存在元素数量在访问堆顶元素时(获取堆顶和出堆时都要)先判断哈希表中该元素是否要删除,并进行删除,直至获得一个真正存在的元素。而入堆时可以抵消哈希表该元素的删除次数,否则再入堆
3.模版:

class LazyHeap{// 最大堆和最小堆只有初始定义不同,其他都完全相同priority_queue<int> pq; // 最大堆// priority_queue<int,vector<int>,greater<int>> pq; // 最小堆map<int,int> mp; // 删除元素-删除次数size_t sz=0; // 堆实际大小// 正式执行删除操作void apply_remove(){while(!pq.empty() && mp[pq.top()]>0){--mp[pq.top()];pq.pop();}}
public:// 返回堆的实际大小size_t size(){return sz;}// 删除void remove(int x){++mp[x];--sz;}// 查看堆顶int top(){// 先正式执行删除操作apply_remove();return pq.top(); // 真正堆顶}// 出堆int pop(){// 先正式执行删除操作apply_remove();--sz;int x=pq.top();pq.pop();return x;}// 入堆void push(int x){if(mp[x]>0) --mp[x]; // 抵消之前的删除else pq.push(x);++sz; // 都要加}
}
2.题目描述

1(学习).设计一个数字容器系统,可以实现以下功能:

  • 在系统中给定下标处 插入 或者 替换 一个数字。
  • 返回 系统中给定数字的最小下标
    请你实现一个 NumberContainers 类:
  • NumberContainers() 初始化数字容器系统。
  • void change(int index, int number) 在下标 index 处填入 number 。如果该下标 index 处已经有数字了,那么用 number 替换该数字。
  • int find(int number) 返回给定数字 number 在系统中的最小下标。如果系统中没有 number ,那么返回 -1 。
    3(学习).设计一个支持下述操作的食物评分系统:
  • 修改 系统中列出的某种食物的评分。
  • 返回系统中某一类烹饪方式下评分最高的食物
    实现 FoodRatings 类:
  • FoodRatings(String[] foods, String[] cuisines, int[] ratings) 初始化系统。食物由 foodscuisines 和 ratings 描述,长度均为 n 。
    • foods[i] 是第 i 种食物的名字。
    • cuisines[i] 是第 i 种食物的烹饪方式。
    • ratings[i] 是第 i 种食物的最初评分。
  • void changeRating(String food, int newRating) 修改名字为 food 的食物的评分。
  • String highestRated(String cuisine) 返回指定烹饪方式 cuisine 下评分最高的食物的名字。如果存在并列,返回 字典序较小 的名字。
    注意,字符串 x 的字典序比字符串 y 更小的前提是:x 在字典中出现的位置在 y 之前,也就是说,要么 x 是 y 的前缀,或者在满足 x[i] != y[i] 的第一个位置 i 处,x[i] 在字母表中出现的位置在 y[i] 之前。
    4.你需要在一个集合里动态记录 ID 的出现频率。给你两个长度都为 n 的整数数组 nums 和 freq ,nums 中每一个元素表示一个 ID ,对应的 freq 中的元素表示这个 ID 在集合中此次操作后需要增加或者减少的数目。
  • 增加 ID 的数目:如果 freq[i] 是正数,那么 freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会添加到集合中。
  • 减少 ID 的数目:如果 freq[i] 是负数,那么 -freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会从集合中删除。
    请你返回一个长度为 n 的数组 ans ,其中 ans[i] 表示第 i 步操作后出现频率最高的 ID 数目 ,如果在某次操作后集合为空,那么 ans[i] 为 0 。
3.学习经验

1.一般更新/替换操作有两步:
(1)现在状态哈希映射更新
(2)堆插入新元素(键不同,值相同[[九.堆(优先队列)#1. 2349. 设计数字容器系统(中等,学习)]],或键相同,值不同[[九.堆(优先队列)#3. 2353. 设计食物评分系统(中等,学习)]])
2.题目中要查询最高/最低能想到堆,出现任意更新/删除堆中元素想到懒删除堆

1. 2349. 设计数字容器系统(中等,学习)

2349. 设计数字容器系统 - 力扣(LeetCode)

思想

1.设计一个数字容器系统,可以实现以下功能:

  • 在系统中给定下标处 插入 或者 替换 一个数字。
  • 返回 系统中给定数字的最小下标。
    请你实现一个 NumberContainers 类:
  • NumberContainers() 初始化数字容器系统。
  • void change(int index, int number) 在下标 index 处填入 number 。如果该下标 index 处已经有数字了,那么用 number 替换该数字。
  • int find(int number) 返回给定数字 number 在系统中的最小下标。如果系统中没有 number ,那么返回 -1 。
    2.因为下标-数是一一对应的关系,所以无需像模版一样记录下标的删除次数,而是直接记录当前下标-数的匹配,当调用find方法时,利用number找到的下标,再用下标得到当前真实的数,判断是否与number一致,从而可知是否被替换
    3.注意:因为要实际删除,所以要写成引用,不能拷贝:
    auto& pq = it->second; // 引用,因为要实际删除
代码
class NumberContainers {
public:map<int, priority_queue<int, vector<int>, greater<int>>>mp;              // 数-下标小顶堆map<int, int> mpidx; // 现在的下标-数NumberContainers() {}void change(int index, int number) {mpidx[index] = number;mp[number].push(index); // 直接插入,find再删除}int find(int number) {auto it = mp.find(number);if (it == mp.end())return -1;auto& pq = it->second; // 引用,因为要实际删除// 现在下标的数与查询数不一致,说明被替换,出队列while (!pq.empty() && mpidx[pq.top()] != number)pq.pop();return pq.empty() ? -1 : pq.top();}
};/*** Your NumberContainers object will be instantiated and called as such:* NumberContainers* obj = new NumberContainers();* obj->change(index,number);* int param_2 = obj->find(number);*/
2. 3607. 电网维护(中等,学习dfs建图,等到dfs学完再写一遍,先暂时过,懒删除堆逻辑没有问题)

3607. 电网维护 - 力扣(LeetCode)

思想
代码
3. 2353. 设计食物评分系统(中等,学习)

2353. 设计食物评分系统 - 力扣(LeetCode)

思想

1.设计一个支持下述操作的食物评分系统:

  • 修改 系统中列出的某种食物的评分。
  • 返回系统中某一类烹饪方式下评分最高的食物。
    实现 FoodRatings 类:
  • FoodRatings(String[] foods, String[] cuisines, int[] ratings) 初始化系统。食物由 foodscuisines 和 ratings 描述,长度均为 n 。
    • foods[i] 是第 i 种食物的名字。
    • cuisines[i] 是第 i 种食物的烹饪方式。
    • ratings[i] 是第 i 种食物的最初评分。
  • void changeRating(String food, int newRating) 修改名字为 food 的食物的评分。
  • String highestRated(String cuisine) 返回指定烹饪方式 cuisine 下评分最高的食物的名字。如果存在并列,返回 字典序较小 的名字。
    注意,字符串 x 的字典序比字符串 y 更小的前提是:x 在字典中出现的位置在 y 之前,也就是说,要么 x 是 y 的前缀,或者在满足 x[i] != y[i] 的第一个位置 i 处,x[i] 在字母表中出现的位置在 y[i] 之前。
    2.很明显的懒删除堆,但跟1不同,这里是修改同一种食物的评分,所以把这个也当做新状态存入堆中,highestRated逻辑依旧是堆顶元素评分与现有评分不一致(不必判断小于,大于),则弹出堆(不必插入堆)
代码
class FoodRatings {
public:struct Node {string food;int rate;Node(string _food, int _rate) : food(_food), rate(_rate) {}};struct cmp {bool operator()(const Node& a, const Node& b) {if (a.rate != b.rate)return a.rate < b.rate;return a.food > b.food;}};map<string, priority_queue<Node, vector<Node>, cmp>>mp;                     // cuisine-<ratring,food>大顶堆map<string, int> curRate;   // 现在food-ratingmap<string, string> belong; // food-cuisineFoodRatings(vector<string>& foods, vector<string>& cuisines,vector<int>& ratings) {int n = foods.size();for (int i = 0; i < n; ++i) {mp[cuisines[i]].push(Node(foods[i], ratings[i]));curRate[foods[i]] = ratings[i];belong[foods[i]] = cuisines[i];}}void changeRating(string food, int newRating) {curRate[food] = newRating;mp[belong[food]].push(Node(food, newRating)); // 直接插入新的,查询时删除旧的}string highestRated(string cuisine) {auto it = mp.find(cuisine);if (it == mp.end())return "";auto& pq = it->second;while (!pq.empty() && curRate[pq.top().food] != pq.top().rate) {pq.pop(); // 删除旧数据}return pq.empty() ? "" : pq.top().food;}
};/*** Your FoodRatings object will be instantiated and called as such:* FoodRatings* obj = new FoodRatings(foods, cuisines, ratings);* obj->changeRating(food,newRating);* string param_2 = obj->highestRated(cuisine);*/
4. 3092. 最高频率的ID(中等)

3092. 最高频率的 ID - 力扣(LeetCode)

思想

1.你需要在一个集合里动态记录 ID 的出现频率。给你两个长度都为 n 的整数数组 nums 和 freq ,nums 中每一个元素表示一个 ID ,对应的 freq 中的元素表示这个 ID 在集合中此次操作后需要增加或者减少的数目。

  • **增加 ID 的数目:**如果 freq[i] 是正数,那么 freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会添加到集合中。
  • **减少 ID 的数目:**如果 freq[i] 是负数,那么 -freq[i] 个 ID 为 nums[i] 的元素在第 i 步操作后会从集合中删除。
    请你返回一个长度为 n 的数组 ans ,其中 ans[i] 表示第 i 步操作后出现频率最高的 ID 数目 ,如果在某次操作后集合为空,那么 ans[i] 为 0 。
    2.找频率最高的ID数目,想到最大堆,元素为数目-值,要动态更新元素的数目,想到懒删除堆,利用哈希表记录当前值-数目,查询时先将值记录数目与当前数目不等的堆顶删除,直至满足条件。
代码
class Solution {
public:typedef long long ll;typedef pair<ll, int> PII; vector<long long> mostFrequentIDs(vector<int>& nums, vector<int>& freq) {int n = nums.size();priority_queue<PII> pq; // cnt-valmap<int, ll> mp; // val-cntfor (int i = 0; i < n; ++i) {pq.push({0, nums[i]});mp[nums[i]] = 0;}vector<ll> res;for (int i = 0; i < n; ++i) {mp[nums[i]] += freq[i];pq.push({mp[nums[i]], nums[i]});while (!pq.empty() && mp[pq.top().second] != pq.top().first)pq.pop();res.push_back(pq.top().first);}return res;}
};

七、对顶堆(滑动窗口第K小/大)

1.套路

1.不再是获取堆顶最大元素,而是每轮查询获取动态的第K小/大,需用两个堆顶性质相反的对顶堆维护,拥有两种操作:
查询:两个堆的堆顶元素实现
插入:分类讨论插入哪个堆,最终可以合并为插入一个堆,之后再从这个堆插入另一个堆

2.题目描述

1.一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。如果有两个景点的评分一样,那么 字典序较小 的景点更好。
你需要搭建一个系统,查询景点的排名。初始时系统里没有任何景点。这个系统支持:

  • 添加 景点,每次添加 一个 景点。
  • 查询 已经添加景点中第 i 好 的景点,其中 i 是系统目前位置查询的次数(包括当前这一次)。
    • 比方说,如果系统正在进行第 4 次查询,那么需要返回所有已经添加景点中第 4 好的。
      注意,测试数据保证 任意查询时刻 ,查询次数都 不超过 系统中景点的数目。
      请你实现 SORTracker 类:
  • SORTracker() 初始化系统。
  • void add(string name, int score) 向系统中添加一个名为 name 评分为 score 的景点。
  • string get() 查询第 i 好的景点,其中 i 是目前系统查询的次数(包括当前这次查询)。
    2(学习).中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。
    实现 MedianFinder 类:
  • MedianFinder() 初始化 MedianFinder 对象。
  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
3.学习经验

1.与简单的top-k问题相比,top-k问题每次查询获取固定的第K小/大,只需要一个堆
而对顶堆是每次查询时获取不固定的第K小/大,所以需要两个堆

1. 2102. 序列顺序查询(困难)

2102. 序列顺序查询 - 力扣(LeetCode)

思想

1.一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。如果有两个景点的评分一样,那么 字典序较小 的景点更好。
你需要搭建一个系统,查询景点的排名。初始时系统里没有任何景点。这个系统支持:

  • 添加 景点,每次添加 一个 景点。

  • 查询 已经添加景点中第 i  的景点,其中 i 是系统目前位置查询的次数(包括当前这一次)。

    • 比方说,如果系统正在进行第 4 次查询,那么需要返回所有已经添加景点中第 4 好的。
      注意,测试数据保证 任意查询时刻 ,查询次数都 不超过 系统中景点的数目。
      请你实现 SORTracker 类:
  • SORTracker() 初始化系统。

  • void add(string name, int score) 向系统中添加一个名为 name 评分为 score 的景点。

  • string get() 查询第 i 好的景点,其中 i 是目前系统查询的次数(包括当前这次查询)。
    2.用两个堆,一个堆pre记录已查询到的好景点,一个堆cur记录剩下的未查询到的景点,cur堆的堆顶就是查询答案。现在考虑添加和查询流程:
    查询:
    cur堆顶为答案,cur弹出堆顶,堆顶入pre
    添加,两种情况:

  • (1)入堆元素处于pre堆,pre堆最差景点入cur

  • (2)入堆元素处于cur堆,直接入

  • 所以合并为都先入pre堆,pre堆最差景点入cur
    因为要获得pre堆最差景点,所以它的性质与cur堆相反,是“最大堆和最小堆”

代码
class SORTracker {
public:struct Node {string name;int score;Node(string _name, int _score) : name(_name), score(_score) {}};struct cmp1 {bool operator()(const Node& a, const Node& b) {if (a.score != b.score)return a.score < b.score;return a.name > b.name;}};struct cmp2 {bool operator()(const Node& a, const Node& b) {if (a.score != b.score)return a.score > b.score;return a.name < b.name;}};priority_queue<Node, vector<Node>, cmp1> cur;priority_queue<Node, vector<Node>, cmp2> pre; // 已遍历过的好景点SORTracker() {}void add(string name, int score) {pre.push(Node(name, score));cur.push(pre.top());pre.pop();}string get() {auto tmp = cur.top();cur.pop();string res = tmp.name;pre.push(tmp);return res;}
};/*** Your SORTracker object will be instantiated and called as such:* SORTracker* obj = new SORTracker();* obj->add(name,score);* string param_2 = obj->get();*/
2. 295. 数据流的中位数(困难,学习)

295. 数据流的中位数 - 力扣(LeetCode)

思想

1.中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。
    实现 MedianFinder 类:
  • MedianFinder() 初始化 MedianFinder 对象。
  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
    2.这题的意思是能够快速找到一个有序列表的中位数,中位数定义为"可将数值集合划分为相等的两部分",所以可以将有序列表分为两部分leftright,
    保证left中所有元素小于等于right中所有元素,同时人为定义0<=left.size()-right.size()<=1,那么中位数就来自 left的最大值(大顶堆)和right的最小值(小顶堆)
    计算的中位数就来自`left
    查询中位数分类讨论:
  • (1)有序列表长度为奇数,即left.size()-right.size()==1,中位数就是left的最大值,如left=[1,2],right=[3]
  • (2)有序列表长度为偶数,即left.size()==right.size(),中位数就是left的最大值和right的最小值的平均,如left=[1,2],right=[3,4]
    插入数分类讨论:
  • (1)left.size()-right.size()==1,
    • (I)val小,插入left,导致不平衡,需将left的最大值放入right
    • (II)val大,插入right,无需调整
    • 所以上述两种情况可以合并val都先插入left,然后将left最大值放入right
  • (2)left.size()==right.size(),
    • (I)val小,插入left,无需调整
    • (II)val大,插入right,导致不平衡,需将right的最小值插入left
    • 所以上述两种情况可以合并val都先插入right,然后将right最小值放入left
      上述逻辑要获取left的最大值和right的最小值,且能够插入元素,所以left为大顶堆,right为小顶堆
代码
class MedianFinder {
public:priority_queue<int> left;                             // 大顶堆priority_queue<int, vector<int>, greater<int>> right; // 小顶堆MedianFinder() {}void addNum(int num) {if (left.size() == right.size()) {right.push(num);left.push(right.top());right.pop();} else {left.push(num);right.push(left.top());left.pop();}}double findMedian() {if (left.size() == right.size()) {return 1.0 * (left.top() + right.top()) / 2;} else {return left.top();}}
};/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder* obj = new MedianFinder();* obj->addNum(num);* double param_2 = obj->findMedian();*/
http://www.dtcms.com/a/348587.html

相关文章:

  • 解密 Spring Boot 自动配置:原理、流程与核心组件协同
  • 人形机器人——电子皮肤技术路线:压电式电子皮肤及一种超越现有电子皮肤NeuroDerm的设计
  • 深度学习:CUDA、PyTorch下载安装
  • Leetcode 3659. Partition Array Into K-Distinct Groups
  • sqlite创建数据库,创建表,插入数据,查询数据的C++ demo
  • 商密保护迷思:经营秘密到底需不需要鉴定?
  • 对称二叉树
  • 机械学习综合练习项目
  • jar包项目自启动设置ubuntu
  • [论文阅读] 软件工程 | GPS算法:用“路径摘要”当向导,软件模型检测从此告别“瞎找bug”
  • 服务器硬件电路设计之 SPI 问答(四):3 线 SPI、Dual SPI 与 Qual SPI 的奥秘
  • 春秋云镜 Hospital
  • Vue 3多语言应用开发实战:vue-i18n深度解析与最佳实践
  • 线程包括哪些状态?线程状态之间是如何变化的?
  • yggjs_rlayout框架v0.1.2使用教程 02 TechLayout 布局组件
  • 拿AI下围棋 -- 开源项目leela-zero
  • ​Mac用户安装JDK 22完整流程(Intel版dmg文件安装指南附安装包下载)​
  • mysql历史社区版本下载
  • 面试题及解答:掌握Linux下常用性能分析工具
  • (Redis)过期删除策略
  • 半年网络安全转型学习计划表(每天3小时)
  • Highcharts推出OEM许可证中国区正式上线:赋能企业级嵌入式数据可视化解决方案
  • 如何使用 DeepSeek 助力工作​。​
  • 数据可视化——matplotlib库
  • EPWpy教程:一个脚本完成能带、声子、电声耦合、弛豫时间计算
  • [自用笔记]上传本地项目至github
  • 联想win11笔记本音频失效,显示差号(x)
  • 【嵌入式DIY实例-ESP32篇】-物联网电能表
  • 硬件开发_基于物联网的宠物猫饲养系统
  • 中介者模式与几个C++应用实例