Greedy Algorithm -- 安排会议室、路灯问题

贪心算法(Greedy Algorithm),一定要证明由算法所得到的解是最优的,通过局部最优可以得到全局最优。

特点

  1. 最自然智慧的算法;
  2. 用一种局部最功利的标准,总是做出在当前看来是最好的选择;
  3. 难点在于证明局部最功利的标准可以得到全局最优解;
  4. 对于贪心算法的学习主要以增加阅历和经验为主。

贪心算法求解的标准过程

  1. 分析业务;
  2. 根据业务逻辑找到不同的解题策略;
  3. 对于能举出反例的策略直接跳过,不能举出反例的策略要证明有效性,这往往是特别困难的,要求数学能力很高且不具有统一的技巧性。

贪心算法的解题套路

  1. 实现一个不依靠贪心策略的解法X,可以用暴力尝试;
  2. 脑补出贪心策略A、贪心策略B、贪心策略C;
  3. 用解法X验证哪个贪心策略正确;
  4. 不要去纠结贪心策略的证明

贪心算法练习

路灯问题

题目描述

小Q正在给一条长度为n的道路设计路灯安置方案。
为了让问题更简单,小Q把道路视为n个方格,需要照亮的地方用’.‘表示, 不需要照亮的障碍物格子用’X’表示。
小Q现在要在道路上设置一些路灯, 对于安置在pos位置的路灯, 这盏路灯可以照亮pos - 1, pos, pos + 1这三个位置。
小Q希望能安置尽量少的路灯照亮所有’.'区域, 希望你能帮他计算一下最少需要多少盏路灯。

Python

class Light:
    """
    https://www.nowcoder.com/questionTerminal/bb1ab2fc0f42419f986b0adc74b38398
    路灯布局,要求照亮所有地方,且使用路灯数最少
    """
    def minLight1(self, road: str):
    	"""
    	暴力求解
    	"""
        if not road:
            return 0
        return self.process([i for i in road], 0, set())

    def process(self, arr: List[str], index: int, lights: Set[int]):
        """
        :param arr: arr[0..index-1]已经做完决定了,那些放了灯的位置,存在lights里
        :param index: arr[index....]位置,自由选择放灯还是不放灯
        :param lights:
        :return: 要求选出能照亮所有.的方案,并且在这些有效的方案中,返回最少需要几个灯
        """
        if index == len(arr):  # arr 遍历结束
            for i in range(len(arr)):
                if arr[i] != 'X':
                    if not (i-1) in lights and not i in lights and not (i+1) in lights:
                        return float('inf')
            return len(lights)
        else:  # arr 还没结束
            # index 不放灯,直接下一个
            no = self.process(arr, index+1, lights)
            yes = float('inf')
            if arr[index] == '.':
                lights.add(index)
                yes = self.process(arr, index+1, lights)
                lights.remove(index)
            return min(no, yes)

    def minLight2(self, road: str):
        """
        贪心法
        :param road:
        :return:
        """
        arr = [i for i in road]
        i, light = 0, 0
        while i < len(arr):
            if arr[i] == 'X':
                i += 1
            else:
                light += 1
                if i + 1 == len(arr):
                    break
                else:
                    if arr[i + 1] == 'X':
                        i = i + 2
                    else:
                        i = i + 3
        return light

安排会议室

题目描述
在大公司里,会议是很多的,开会得有场子,要场子你得先在电子流里预订。
如果你是项目组新来的小弟,那么恭喜你,每天抢订会议室的任务就光荣的分给你了。
老大要求你尽可能多的订会议室,但是这些会议室之间不能有时间冲突。

Python

class BestArrange:

    class Program:
        def __init__(self, start: int, end: int):
            self.start = start
            self.end = end

    def bestArrange1(self, programs: List[Program]):
        """
        暴力方法
        :param programs:
        :return:
        """
        if not programs or len(programs) == 0:
            return 0
        return self.process(programs, 0, 0)

    def copyButExcept(self, arr: List[Program], index: int) -> List[Program]:
        copy = list()
        for i in range(len(arr)):
            if i != index:
                copy.append(arr[i])
        return copy

    def process(self, programs: List[Program], done: int, timeline: int):
        """
        目前来到timeLine的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排
        :param programs: 还剩下的会议都放在programs里
        :param done: done之前已经安排了多少会议的数量
        :param timeline: timeLine目前来到的时间点是什么
        :return: 能安排的最多会议数量
        """
        if len(programs) == 0:
            return done
        # 还剩下会议
        max_value = done
        for i in range(len(programs)):
            if programs[i].start >= timeline:
                next_programs = self.copyButExcept(programs, i)
                max_value = max(max_value, self.process(next_programs, done + 1, programs[i].end))

        return max_value

    def bestArrange2(self, programs: List[Program]):
        """
        贪心法,按结束时间早的  优先安排
        :param programs:
        :return:
        """
        # 按结束时间排序
        sorted_prog = sorted(programs, key=lambda x: x.end, reverse=False)
        timeline = 0
        res = 0
        for i in sorted_prog:
            if timeline <= i.start:
                res += 1
                timeline = i.end
        return res

切割金条

class LessMoneySplitGold:
    """
    题目:
    一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。

    问:一群人想整分整块金条,怎么分最省铜板?
    例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。
    金条要分成10,20,30。如果先把长度60的金条分成10和50,花费60;再把长度50的金条分成20和30,花费50;一共花费110铜板。
    但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,花费30;一共花费90铜板。
    输入一个数组,返回分割的最小代价。
    """
    def solution(self, arr: List[int]):
        if not arr:
            return 0
        return self.process(arr, 0)

    def process(self, arr: List[int], pre: int):
        """
        暴力法
        :param arr: 等待合并的数都在arr里,pre之前的合并行为产生了多少总代价
        :param pre:
        :return: arr中只剩一个数字的时候,停止合并,返回最小的总代价
        """
        if len(arr) == 1:
            return pre

        ans = float('inf')
        for i in range(len(arr)):
            for j in range(i+1, len(arr)):
                ans = min(ans, self.process(self.copyAndMergeTwo(arr, i, j), pre+arr[i]+arr[j]))

        return ans

    def copyAndMergeTwo(self, arr: List[int], i: int, j: int):
        ans = [0] * (len(arr) - 1)
        ansi = 0
        for arri in range(len(arr)):
            if arri != i and arri != j:
                ans[ansi] = arr[arri]
                ansi += 1

        ans[ansi] = arr[i] + arr[j]
        return ans

    def solution2(self, arr: List[int]):
        """
        贪心法,结合小根堆,一次弹出最小的两个元素,然后将其之和再放进堆里,
        直到堆内元素个数为1个时,停止。
        :param arr:
        :return:
        """
        # 转换成堆结构
        heapq.heapify(arr)
        ans = 0
        while len(arr) > 1:
            item1, item2 = heapq.heappop(arr), heapq.heappop(arr)
            add = item1 + item2
            heapq.heappush(arr, add)
            ans += add
        return ans

项目最大利润

class IPO:
    """
    输入:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1]
    输出:4
    解释:
    由于你的初始资本为 0,你仅可以从 0 号项目开始。
    在完成后,你将获得 1 的利润,你的总资本将变为 1。
    此时你可以选择开始 1 号或 2 号项目。
    由于你最多可以选择两个项目,所以你需要完成 2 号项目以获得最大的资本。
    因此,输出最后最大化的资本,为 0 + 1 + 3 = 4。

    链接:https://leetcode.cn/problems/ipo
    """
    class Program_p:
        def __init__(self, p: int, c: int):
            self.p = p
            self.c = c

        def __lt__(self, other):
            if self.p >= other.p:
                return True
            else:
                return False

    class Program_c:
        def __init__(self, p: int, c: int):
            self.p = p
            self.c = c

        def __lt__(self, other):
            if self.c < other.c:
                return True
            else:
                return False

    def solution(self, k, w, profits: List[int], capital: List[int]):
        """

        :param k: 最多轮数
        :param w: 初始资本
        :param profits: 利润表
        :param capital: 成本表
        :return:
        """
        # 成本小根堆
        heaq_c = []
        # 利润大根堆
        heap_p = []
        for i in range(len(profits)):
            heapq.heappush(heaq_c, IPO.Program_c(p=profits[i], c=capital[i]))

        for i in range(k):
            while heaq_c and heaq_c[0].c <= w:
                _obj = heapq.heappop(heaq_c)
                heapq.heappush(heap_p, IPO.Program_p(_obj.p, _obj.c))

            if not heap_p:
                return w

            w += heapq.heappop(heap_p).p

        return w

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NLP_wendi

谢谢您的支持。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值