堆在贪心中的运用题汇总

Posted C_YCBX Py_YYDS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆在贪心中的运用题汇总相关的知识,希望对你有一定的参考价值。

题目一


OJ平台

题目解析

题目给出的数据是每天生产出来的苹果数量,以及该苹果对应的过期时间,而题目通过限制每天只能吃一个苹果。
最后问最多能吃多少个苹果?

很明显,这是一道模拟题,我们模拟从第0天到最后,每天吃一个苹果能延续到第几天,就是能吃多少个苹果。

我们如何模拟呢?

我们需要维护一个二元关系的元素->[过期时间,苹果数量],由于是根据时间来遍历,所以过期时间我们可以正常的算出来,而且也很好判断是否过期。这个时候取苹果就是根据这个过期时间的远近来取的,这也是这题贪心的部分,我们需要取得距离过期时间最近的苹果,也就是在构建每个元素的同时,我们每次需要取出过期时间最近的元素来进行判断。如果以及过期就扔掉,如果未过期就判断这个元素的苹果是否数量大于0,如果这两者都满足就取一个苹果,否则直接扔掉。这个过程反应在代码里面就是一个优先队列元素的更改和出队的过程。接下来查看下面的解题代码应该就很懂了!注意题目给出的苹果树数据我们都需要完全的给他入队,不要留空挡。(这个可查看力扣官方题解的动画描述来理解)

解题代码

cpp代码

class Solution 
public:
//贪心例题:从第一天开始往后遍历,先把最先容易腐烂变质的吃掉。
//如何实现这一过程的模拟?这就需要用到优先队列一直将最容易变质的取下来吃掉。如果已经变质则直接扔掉
//如何判断是否变质?根据给出的日期维护一个二元的整体,一旦变质日期不大于当前日期,则变质了。
    int eatenApples(vector<int>& apples, vector<int>& days) 
        int n = apples.size();
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>pq;
        int sum = 0;  //存储答案
        int date = 0; //TODO 当前日期,从第0天开始记
        while(!pq.empty()||date<n)//TODO 优先队列不为空||还有数据未取完
            //TODO 随着日期的进行,同时也更新队列中的数据:每个元素由(过期时间,苹果数量)组成
            if(date<n&&days[date])
                pq.push(make_pair(days[date]+date-1,apples[date]));
            //TODO 把 已经过期的||已经用完的 苹果排除
            while(!pq.empty()&&(date>pq.top().first||pq.top().second==0))
               pq.pop();
            //TODO 每过一天就吃一个最接近过期苹果
            if(!pq.empty()) 
               sum++;
               const_cast<pair<int,int>&>(pq.top()).second--;//这里注意强转才能改数据
            
            date++; //不断更新日期
        
        return sum;
    
;

go语言

func eatenApples(apples, days []int) (ans int) 
    h := hp
    i := 0
    for ; i < len(apples); i++ 
        for len(h) > 0 && h[0].end <= i 
            heap.Pop(&h)
        
        if apples[i] > 0 
            heap.Push(&h, pairi + days[i], apples[i])
        
        if len(h) > 0 
            h[0].left--
            if h[0].left == 0 
                heap.Pop(&h)
            
            ans++
        
    
    for len(h) > 0 
        for len(h) > 0 && h[0].end <= i 
            heap.Pop(&h)
        
        if len(h) == 0 
            break
        
        p := heap.Pop(&h).(pair)
        num := min(p.end-i, p.left)
        ans += num
        i += num
    
    return


type pair struct end, left int 
type hp []pair

func (h hp) Len() int             return len(h) 
func (h hp) Less(i, j int) bool   return h[i].end < h[j].end 
func (h hp) Swap(i, j int)        h[i], h[j] = h[j], h[i] 
func (h *hp) Push(v interface)  *h = append(*h, v.(pair)) 
func (h *hp) Pop() interface    a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v 

func min(a, b int) int 
    if a > b 
        return b
    
    return a

题目二

解题思路

这道题可谓是深有感触,最近期末考试正好正在进行中,我的复习(预习)也正在进行中,我是一个经常逃课的孩子(…)

大家在期末复习的时候怎么进行先后复习的呢?
我们肯定是先复习距离考试时间最近的科目啊。而这道题给的就是每次只能处理一个科目的复习,中间不间断,然后给了你每个科目的复习时间。这就导致如果有些科目虽然是距离DDL时间近,但如果我们复习完它之后就会导致其他的DDL远一些的无法完成复习,

具体举例子如下:

假设 duration 数组是这样的 大学物理 [2, 4] 高等数学 [5, 7] 线性代数 [3, 3] 那么,按照策略,我们会先尝试修读线性代数。 然后尝试修读大学物理,这个时候我们发现竟然修了线性代数之后就修不了大学物理了;只能放弃;转而修高等数学发现也没有足够的时间了。

那合理的做法是什么呢? 就是我们这个时候需要放弃线性代数,因为线性代数和大学物理的权重是没有区别的,既然两者只能修一门,我们应该让当前累计花费的时间更少。大学物理虽然ddl比线性代数更晚,但它修读的时间也更短,如果已经要放弃一门课了,我们会放弃修读时间更长的。

总结:

  1. 我们先复习DDL时间比较近的。
  2. 如果出现复习时间超出DDL,则需要进行优化,放弃之前选择过的需要花时间最多的科目,用当前这个进行替代,能减少当前耗时!(这个的前提条件建立在优先选择DDL近的,因为这样按照时间排序后,一旦产生超过的,只要和前面最大的进行替换,耗时肯定会变得更小,所以就不会超过DDL了)

解题代码

根据解题思路得出代码的书写思路:

  1. 优先复习DDL时间近的–>利用DDL从小到大对其进行排序。
  2. 超出DDL时的替换过程–>利用优先队列进行操作即可。

cpp解法

class Solution 
public:
    int scheduleCourse(vector<vector<int>>& courses) 
        sort(courses.begin(), courses.end(), [](const auto& a, const auto& b)  return a[1] < b [1];);

        priority_queue<int> q;
        int day = 0;
        for (const auto& c: courses) 
            if (day + c[0] <= c[1]) 
                day += c[0];
                q.push(c[0]);
                continue;
            
            if (!q.empty() && q.top() > c[0]) 
                day -= q.top();
                day += c[0];
                q.pop();
                q.push(c[0]);
             
            
        

        return q.size();
    
;

go语言有点麻烦,还需要实现一些接口才能使用Heap

go语言

func scheduleCourse(courses [][]int) int 
    sort.Slice(courses, func(i, j int) bool 
        return courses[i][1] < courses[j][1]
    )

    h := &IntHeap
    total := 0 // 优先队列中所有课程的总时间
    for _, course := range courses 
        if t := course[0]; total+t <= course[1] 
            total += t
            heap.Push(h, t)
         else if h.Len() > 0 && t < (*h)[0] 
            total += t - (*h)[0]
            heap.Pop(h)
            heap.Push(h,t)
        
    
    return h.Len()


type IntHeap []int  // 定义一个类型

func (h IntHeap) Len() int  return len(h)   // 绑定len方法,返回长度
func (h IntHeap) Less(i, j int) bool   // 绑定less方法
	return h[i] > h[j]  // 如果h[i]<h[j]生成的就是小根堆,如果h[i]>h[j]生成的就是大根堆

func (h IntHeap) Swap(i, j int)   // 绑定swap方法,交换两个元素位置
	h[i], h[j] = h[j], h[i]


func (h *IntHeap) Pop() interface   // 绑定pop方法,从最后拿出一个元素并返回
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x


func (h *IntHeap) Push(x interface)   // 绑定push方法,插入新元素
	*h = append(*h, x.(int))

题目三

解法一:堆+集合

过程很简单就是通过最小堆得到每次选择操作后的最小值,再根据这个最小值扩散,如果过程中出现了重复的,就用set去重就可。

class Solution 
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) 
        using ll = long long;
        priority_queue<ll,vector<ll>,greater<ll> >Q;
        unordered_set<ll>check;check.emplace(1);
        Q.push(1);
        int sz = primes.size();
        int res;
        while(n--)
            res = Q.top();Q.pop();
            for(int i=0;i<sz;i++)
                if(!check.count((ll)res*primes[i]))
                    Q.push((ll)res*primes[i]);
                    check.emplace((ll)res*primes[i]);
                    
            
        
        return res;
    
;

解法二:动态规划==找规律解决

简而言之,就是每次取完一个值后便把这个值对应的指针往后移动。

class Solution 
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) 
        int dp[n];
        int sz = primes.size();
        memset(dp,0,sizeof(dp));
        dp[0] = 1;
        int pointer[sz];memset(pointer,0,sizeof pointer);
        for(int i=1;i<n;i++)
            int res = INT_MAX;
            for(int j=0;j<sz;j++)
                res = min(dp[pointer[j]]*primes[j],res);
            
            dp[i] = res;
            //去重,指针偏移
            for(int j=0;j<sz;j++)
                if(dp[pointer[j]]*primes[j]==dp[i])
                    pointer[j]++;
            
        
        return dp[n-1];
    
;

以上是关于堆在贪心中的运用题汇总的主要内容,如果未能解决你的问题,请参考以下文章

贪心热门问题8:划分字母区间

2022华数杯A题 B题 C题 思路汇总

毒瘤思维题汇总

好题收集 异或最大值(贪心,trie树)

字节跳动2019春招研发部分编程题汇总题解

HTML面试题汇总