2021秋招-算法-贪心算法之区间调度问题

算法-贪心算法之区间调度问题

大佬总结-贪心算法之区间调度问题

什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。

比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。

什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一小部分问题拥有这个性质。

比如你面前放着 100 张人民币,你只能拿十张,怎么才能拿最多的面额?显然每次选择剩下钞票中面值最大的一张,最后你的选择一定是最优的。

然而,大部分问题都明显不具有贪心选择性质。比如打斗地主,对手出对儿三,按照贪心策略,你应该出尽可能小的牌刚好压制住对方,但现实情况我们甚至可能会出王炸。这种情况就不能用贪心算法,而得使用动态规划解决,参见前文 动态规划解决博弈问题。

一、问题概述

言归正传,本文解决一个很经典的贪心算法问题 Interval Scheduling(区间调度问题)。给你很多形如[start,end]的闭区间,请你设计一个算法,算出这些区间中最多有几个互不相交的区间

int intervalScheduling(int[][] ints) {}

举个例子,intvs=[[1,3],[2,4],[3,6]],这些区间最多有两个区间互不相交,即[[1,3],[3,6]],你的算法应该返回 2。注意边界相同并不算相交。

这个问题在生活中的应用广泛,比如你今天有好几个活动,每个活动都可以用区间[start,end]表示开始和结束的时间,请问你今天最多能参加几个活动呢?

二、贪心解法

这个问题有许多看起来不错的解决思路,实际上都不能得到正确答案。比如说:

也许我们可以每次选择可选区间中开始最早的那个?但是可能存在某些区间开始很早,但是很长,使得我们错误地错过了一些短的区间

或者我们每次选择可选区间中最短的那个?或者选择出现冲突最少的那个区间?这些方案都能很容易举出反例,不是正确的方案。

正确的思路其实很简单,可以分为以下三步:

  1. 从区间集合 intvs 中选择一个区间 x,这个 x 是在当前所有区间中结束最早的(end 最小)

  2. 把所有与 x 区间相交的区间从区间集合 intvs 中删除。

  3. 重复步骤 1 和 2,直到 intvs 为空为止。之前选出的那些 x 就是最大不相交子集。

把这个思路实现成算法的话,可以按每个区间的end数值升序排序,因为这样处理之后实现步骤 1 和步骤 2 都方便很多:

在这里插入图片描述
现在来实现算法,对于步骤 1,由于我们预先按照end排了序,所以选择 x 是很容易的。关键在于,如何去除与 x 相交的区间,选择下一轮循环的 x 呢?

由于我们事先排了序,不难发现所有与 x 相交的区间必然会与 x 的end相交;如果一个区间不想与 x 的end相交,它的start必须要大于(或等于)x 的end

在这里插入图片描述
下面看下代码:
在这里插入图片描述

三、应用举例

下面举例几道 LeetCode 题目应用一下区间调度算法。

leetcode-435 题,无重叠区间

我们已经会求最多有几个区间不会重叠了,那么剩下的不就是至少需要去除的区间吗?

int eraseOverlapIntervals(int[][] intervals) {
    int n = intervals.length;
    return n - intervalSchedule(intervals);
}
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        
        # 贪心算法:  计算无重叠区间
        intervalLen = len(intervals)
        # base case 
        if intervalLen <= 1:
            return 0
        
        
        # 按照end排序
        sortedIntervals = sorted(intervals, key = lambda x:x[1])
        count = 1
        compareBase = sortedIntervals[0]
        
        for interval in sortedIntervals:
            if interval[0] >= compareBase[1]:
                count += 1
                compareBase = interval
        
        return intervalLen - count 
leetcode-452 题,用最少的箭头射爆气球

在这里插入图片描述

其实稍微思考一下,这个问题和区间调度算法一模一样!如果最多有n个不重叠的区间,那么就至少需要n个箭头穿透所有区间:

在这里插入图片描述
只是有一点不一样,在intervalSchedule算法中,如果两个区间的边界触碰,不算重叠;而按照这道题目的描述,箭头如果碰到气球的边界气球也会爆炸,所以说相当于区间的边界触碰也算重叠:
在这里插入图片描述
所以只要将之前的算法稍作修改,就是这道题目的答案:

int findMinArrowShots(int[][] intvs) {
    // ...

    for (int[] interval : intvs) {
        int start = interval[0];
        // 把 >= 改成 > 就行了
        if (start > x_end) {
            count++;
            x_end = interval[1];
        }
    }
    return count;
}
python自己实现
# 计算划分无重叠区间即可
class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        pointsLen = len(points)
        # base case 
        if pointsLen == 0:
            return 0
        
        # 按照end排序
        sortPoints = sorted(points, key=lambda x:x[1])
        count = 1
        compareBase = sortPoints[0]
        for point in sortPoints:
            if point[0] > compareBase[1]:
                count += 1
                compareBase = point
            
        return count

这么做的原因也不难理解,因为现在边界接触也算重叠,所以start == x_end时不能更新区间 x。

本文终。对于区间问题的处理,一般来说第一步都是排序,相当于预处理降低后续操作难度。但是对于不同的问题,排序的方式可能不同,这个需要归纳总结,以后再写写这方面的文章。

leetcode 贪心算法之区间调度

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言多机调度问题是指将n个作业分配给m台机器,使得完成所有作业的时间最短。这是一个经典的贪心算法问题。在这个问题中,我们需要将n个作业分配给m台机器,使得每台机器的工作时间尽可能平均,从而使得完成所有作业的时间最短。具体实现可以使用两种方法:机器数大于待分配作业数和机器数小于待分配作业数。前者可以直接将每个作业分配给一台机器,后者则需要使用贪心算法,将每个作业分配给当前工作时间最短的机器。 代码实现如下(C语言): ``` #include <stdio.h> #define N 8 //作业数 #define M 5 //机器数 int s[M] = {0,0,0};//每台机器当前已分配的作业总耗时 int main(void) { int time[N] = {17,15,11,9,8,5,4,2};//处理时间按从大到小排序 int maxtime = 0; if(M >= N) { maxtime = setwork1(time,N); } else { maxtime = setwork2(time,N); } printf("最多耗费时间%d。",maxtime); } //机器数大于待分配作业数 int setwork1(int t[],int n) { int i; for(i=0;i<n;i++) { s[i] = t[i]; } int ma = max(s,N); return ma; } //机器数小于待分配作业数 int setwork2(int t[],int n) { int i; int mi = 0; for(i=0;i<n;i++) { mi = min(M); printf("第%d号作业,时间和最小的机器号为%d.时间和为%d:\n",i,mi,s[mi]); s[mi] = s[mi]+t[i]; } int ma = max(s,M); return ma; } //求出目前处理作业的时间和最小的机器号 int min(int m) { int min = 0; int i; for(i=1;i<m;i++) { if(s[min] > s[i]) { min = i; } } return min; } //求最终结果(最长处理时间) int max(int s[],int num) { int max = s[0]; int i; for(i=1;i<num;i++) { if(max < s[i]) { max = s[i]; } } return max; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值