Datawhale Leecode基础算法篇 task04:贪心算法

news2024/12/27 10:30:59

 官方学习文档:datawhalechina

往期task01:枚举算法链接:Datawhale Leecode基础算法篇 task01:枚举算法

往期task02:递归算法and分治算法:Datawhale Leecode基础算法篇 task02:递归算法and分治算法

往期task03:回溯算法:Datawhale Leecode基础算法篇 task03:回溯算法

贪心算法

贪心算法简介

贪心算法(Greedy Algorithm):一种在每次决策时,总是采取在当前状态下的最好选择,从而希望导致结果是最好或最优的算法。

        贪心算法是一种改进的「分步解决算法」,其核心思想是:将求解过程分成「若干个步骤」,然后根据题意选择一种「度量标准」,每个步骤都应用「贪心原则」,选取当前状态下「最好 / 最优选择(局部最优解)」,并以此希望最后得出的结果也是「最好 / 最优结果(全局最优解)」。

        换句话说,贪心算法不从整体最优上加以考虑,而是一步一步进行,每一步只以当前情况为基础,根据某个优化测度做出局部最优选择,从而省去了为找到最优解要穷举所有可能所必须耗费的大量时间。

       不是所有问题,都可以使用贪心算法通过局部最优解而得到整体最优解或者是整体最优解的近似解。使用贪心算法解决的问题必须满足下面的两个特征:

  1. 贪心选择性
  2. 最优子结构

贪心选择性:指的是一个问题的全局最优解可以通过一系列局部最优解(贪心选择)来得到。

换句话说,当进行选择时,我们直接做出在当前问题中看来最优的选择,而不用去考虑子问题的解。在做出选择之后,才会去求解剩下的子问题,如下图所示。

贪心算法在进行选择时,可能会依赖之前做出的选择,但不会依赖任何将来的选择或是子问题的解。运用贪心算法解决的问题在程序的运行过程中无回溯过程

最优子结构性质:指的是一个问题的最优解包含其子问题的最优解。

问题的最优子结构性质是该问题能否用贪心算法求解的关键

如果原问题的最优解包含子问题的最优解,则说明该问题满足最优子结构性质。

反之,如果不能利用子问题的最优解推导出整个问题的最优解,那么这种问题就不具有最优子结构。

        贪心算法最难的部分不在于问题的求解,而在于正确性的证明。我们常用的证明方法有「数学归纳法」和「交换论证法」。

  • 数学归纳法:先计算出边界情况(例如 n=1)的最优解,然后再证明对于每个 n,$F_{n + 1}$都可以由 Fn 推导出。

  • 交换论证法:从最优解出发,在保证全局最优不变的前提下,如果交换方案中任意两个元素 / 相邻的两个元素后,答案不会变得更好,则可以推定目前的解是最优解。

        判断一个问题是否通过贪心算法求解,是需要进行严格的数学证明的。但是在日常写题或者算法面试中,不太会要求大家去证明贪心算法的正确性。

所以,当我们想要判断一个问题是否通过贪心算法求解时,我们可以:

  1. 凭直觉:如果感觉这道题可以通过「贪心算法」去做,就尝试找到局部最优解,再推导出全局最优解。
  2. 举反例:尝试一下,举出反例。也就是说找出一个局部最优解推不出全局最优解的例子,或者找出一个替换当前子问题的最优解,可以得到更优解的例子。如果举不出反例,大概率这道题是可以通过贪心算法求解的。

贪心算法三步走

  1. 转换问题:将优化问题转换为具有贪心选择性质的问题,即先做出选择,再解决剩下的一个子问题。
  2. 贪心选择性质:根据题意选择一种度量标准,制定贪心策略,选取当前状态下「最好 / 最优选择」,从而得到局部最优解
  3. 最优子结构性质:根据上一步制定的贪心策略,将贪心选择的局部最优解和子问题的最优解合并起来,得到原问题的最优解。

贪心算法的应用

分发饼干

题目链接:

  • 455. 分发饼干 - 力扣

描述:一位很棒的家长为孩子们分发饼干。对于每个孩子 i,都有一个胃口值 g[i],即每个小孩希望得到饼干的最小尺寸值。对于每块饼干 j,都有一个尺寸值 s[j]。只有当 s[j]>=g[i] 时,我们才能将饼干 j 分配给孩子 i。每个孩子最多只能给一块饼干。

现在给定代表所有孩子胃口值的数组 g 和代表所有饼干尺寸的数组 j。

要求:尽可能满足越多数量的孩子,并求出这个最大数值。

说明

  • 1≤g.length≤3∗104。
  • 0≤s.length≤3∗104。
  • 1≤g[i],s[j]≤231−1。

示例

  • 示例 1:
输入:g = [1,2,3], s = [1,1]
输出:1
解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1, 2, 3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以应该输出 1。
  • 示例 2:
输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1, 2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2。

 解题思路:

为了尽可能的满⾜更多的⼩孩,而且一块饼干不能掰成两半,所以我们应该尽量让胃口小的孩子吃小块饼干,这样胃口大的孩子才有大块饼干吃。

所以,从贪心算法的角度来考虑,我们首先应该对数组 g 和s进行排序,并且对于每个孩子,应该选择满足这个孩子的胃口且尺寸最小的饼干。

下面我们使用贪心算法三步走的方法解决这道题。

  1. 转换问题:将原问题转变为,当胃口最小的孩子选择完满足这个孩子的胃口且尺寸最小的饼干之后,再解决剩下孩子的选择问题(子问题)。
  2. 贪心选择性质:对于当前孩子,用尺寸尽可能小的饼干满足这个孩子的胃口。
  3. 最优子结构性质:在上面的贪心策略下,当前孩子的贪心选择 + 剩下孩子的子问题最优解,就是全局最优解。也就是说在贪心选择的方案下,能够使得满足胃口的孩子数量达到最大。

使用贪心算法的代码解决步骤描述如下:

  1. 对数组 g、s 进行从小到大排序,使用变量 index_g 和 index_s 分别指向 g、s 初始位置,使用变量 res 保存结果,初始化为 0。
  2. 对比每个元素 g[index_g] 和 s[index_s]:
    1. 如果 g[index_g]≤s[index_s] ,说明当前饼干满足当前孩子胃口,则答案数量加 1,并且向右移动 index_g 和 index_s。
    2. 如果 g[index_g]>s[index_s],说明当前饼干无法满足当前孩子胃口,则向右移动 indexs,判断下一块饼干是否可以满足当前孩子胃口。
  3. 遍历完输出答案 res。

代码:

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        index_g, index_s = 0, 0
        res = 0
        while index_g < len(g) and index_s < len(s):
            if g[index_g] <= s[index_s]:
                res += 1
                index_g += 1
                index_s += 1
            else:
                index_s += 1   

        return res

无重叠区间

题目链接:

  • 435. 无重叠区间 - 力扣

描述:给定一个区间的集合 intervals,其中 intervals[i]=[starti,endi]。从集合中移除部分区间,使得剩下的区间互不重叠(指在数轴上的区间没有重叠区域)。

要求:返回需要移除区间的最小数量。

说明

  • 1≤intervals.length≤105。
  • intervals[i].length==2。
  • −5∗104≤starti<endi≤5∗104。

示例

  • 示例 1:
输入:intervals = [[1,2],[2,3],[3,4],[1,3]]
输出:1
解释:移除 [1,3] 后,剩下的区间没有重叠。
  • 示例 2:
输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

解题思路: 

那我们思考怎么才能让区间不重叠呢?下一个区间的开头大于等于上个区间的结尾吗?那怎么使用贪心算法来解决这个问题呢?

从正着的角度,移除最少几个区间来使剩下的区间没有重合,看起来很难实现,那我们能不能倒过来想呢,让没有重合的区间数最多,总数与之相减就是所求,会不会简单一点?

从贪心算法的角度来考虑,我们首先应该将区间按照结束时间排序。每次选择结束时间最早的区间,然后再在剩下的时间内选出最多的不重叠区间。

我们用贪心三部曲来解决这道题。

  1. 转换问题:将原问题转变为,当选择结束时间最早的区间之后,再在剩下的时间内选出最多的区间(子问题)。
  2. 贪心选择性质:每次选择时,选择结束时间最早的区间。这样选出来的区间一定是原问题最优解的区间之一。
  3. 最优子结构性质:在上面的贪心策略下,贪心选择当前时间最早的区间 + 剩下的时间内选出最多不重叠区间的子问题最优解,就是全局最优解。

使用贪心算法的代码解决步骤描述如下:

  1. 将区间集合按照结束坐标升序排列,然后定义两个变量,一个是当前不重叠区间的结束时间 end_pos,另一个是不重叠区间的个数 count。初始情况下,结束坐标 end_pos 为第一个区间的结束坐标,count 为 1。
  2. 依次遍历每段区间。对于每段区间:intervals[i]:
    1. 如果 end_pos≤intervals[i][0],即 end_pos 小于等于区间起始位置,则说明出现了不重叠区间,令不重叠区间数 count 加 1,end_pos 更新为新区间的结束位置。
  3. 最终返回「总区间个数 - 不重叠区间的最多个数」即 len(intervals)−count 作为答案。

代码:

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        if not intervals:
            return 0
        intervals.sort(key=lambda x: x[1])
        end_pos = intervals[0][1]
        count = 1
        for i in range(1, len(intervals)):
            if end_pos <= intervals[i][0]:
                count += 1
                end_pos = intervals[i][1]

        return len(intervals) - count

首先判断数组是否为空,为空则返回0,然后根据每个区间的结束时间对区间进行排序,end_pos为排序后第一个区间的结束时间,然后进行遍历即可。

练习

柠檬水找零

题目链接:柠檬水找零

描述:一杯柠檬水的售价是 5 美元。现在有 n 个顾客排队购买柠檬水,每人只能购买一杯。顾客支付的钱面额有 5 美元、10 美元、20 美元。必须给每个顾客正确找零(就是每位顾客需要向你支付 5 美元,多出的钱要找还回顾客)。

现在给定 n 个顾客支付的钱币面额数组 bills

要求:如果能给每位顾客正确找零,则返回 True,否则返回 False

说明

  • 一开始的时候手头没有任何零钱。
  • 1≤bills.length≤10^{5}
  • bills[i] 不是 5 就是 10 或是 20。

示例

  • 示例 1:
输入:bills = [5,5,5,10,20]
输出:True
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 True。
  • 示例 2:

输入:bills = [5,5,10,10,20]
输出:False
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 False。

解题思路:

首先,我们看这道题可能会陷入一个思维误区,既然5美元最重要(因其可以用于10和20美元的找零),10美元其次,20美元最后,那按照贪心算法是不是开始5美元越多越好,先要把bills进行一个排序呢?

错,因为这个问题里顾客可能不是同时来的,先来的就必须先做。

那么我们就可以决定对于不同数额分别采取不同策略:

  1. 如果顾客支付 5 美元,直接收下。
  2. 如果顾客支付 10 美元,如果我们手头有 5 美元面额的钞票,则找给顾客,否则无法正确找零,返回 False
  3. 如果顾客支付 20 美元,如果我们手头有 1 张 10 美元和 1 张 5 美元的钞票,或者有 3 张 5 美元的钞票,则可以找给顾客。如果两种组合方式同时存在,倾向于第 1 种方式找零,因为使用 5 美元的场景比使用 10 美元的场景多,要尽可能的保留 5 美元的钞票。如果这两种组合方式都不通知,则无法正确找零,返回 False

代码:

class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        five, ten, twenty = 0, 0, 0
        for bill in bills:
            if bill == 5:
                five += 1
            if bill == 10:
                if five <= 0:
                    return False
                ten += 1
                five -= 1
            if bill == 20:
                if five > 0 and ten > 0:
                    five -= 1
                    ten -= 1
                    twenty += 1
                elif five >= 3:
                    five -= 3
                    twenty += 1
                else:
                    return False

        return True

分发糖果 

题目链接:分发糖果

描述:n 个孩子站成一排。老师会根据每个孩子的表现,给每个孩子进行评分。然后根据下面的规则给孩子们分发糖果:

  • 每个孩子至少得 1 个糖果。
  • 评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果。

现在给定 n 个孩子的表现分数数组 ratings,其中 ratings[i] 表示第 i 个孩子的评分。

要求:返回最少需要准备的糖果数目。

说明

  • n==ratings.length。
  • 1≤n≤2×10^{4}
  • 0≤ratings[i]≤2∗10^{4}

示例

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

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

解题思路:

首先,我们观察示例1发现,孩子的顺序是不能变动的,否则可以将ratings变为[1,2,0],分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果,这样总数就是4了,小于输出的5,不符合题目要求。

那我们怎么满足评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果这个条件呢?分别从前往后和从后往前遍历处理一遍可以吗?从后往前遍历处理会影响之前的步骤吗?

思考后发现,其实是不会的,因为从后往前遍历处理只可能让评分更高的孩子的糖果数更高,而不会影响评分更低的孩子。

这样我们就可以制定规则:

「每个孩子至少得 1 个糖果」:说明糖果数目至少为 N 个。

「评分更高的孩子必须比他两侧相邻位置上的孩子分得更多的糖果」:可以看做为以下两种条件:

  • 当 ratings[i−1]<ratings[i] 时,第 i 个孩子的糖果数量要比第 i−1 个孩子的糖果数量多;
  • 当 ratings[i]>ratings[i+1] 时,第 i 个孩子的糖果数量要比第i+1 个孩子的糖果数量多。

根据以上信息,我们可以设定一个长度为 N 的数组 sweets 来表示每个孩子分得的最少糖果数,初始每个孩子分得糖果数都为 1。

然后遍历两遍数组,第一遍遍历满足当 ratings[i−1]<ratings[i] 时,第 i 个孩子的糖果数量比第 i−1 个孩子的糖果数量多 1 个。第二遍遍历满足当 ratings[i]>ratings[i+1] 时,第 i 个孩子的糖果数量取「第 i+1 个孩子的糖果数量多 1 个」和「第 i 个孩子目前拥有的糖果数量」中的最大值。

然后再遍历求所有孩子的糖果数量和即为答案。

代码:

class Solution:
    def candy(self, ratings: List[int]) -> int:
        size = len(ratings)
        sweets = [1 for _ in range(size)]

        for i in range(1, size):
            if ratings[i] > ratings[i - 1]:
                sweets[i] = sweets[i - 1] + 1

        for i in range(size - 2, -1, -1):
            if ratings[i] > ratings[i + 1]:
                sweets[i] = max(sweets[i], sweets[i + 1] + 1)

        res = sum(sweets)
        return res

 救生艇

题目链接:救生艇

描述:给定一个整数数组 people 代表每个人的体重,其中第 i 个人的体重为 people[i]。再给定一个整数 limit,代表每艘船可以承载的最大重量。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit

要求:返回载到每一个人所需的最小船数(保证每个人都能被船载)。

说明

  • 1≤people.length≤5×10^{4}
  • 1≤people[i]≤limit≤3×10^{4}

示例

  • 示例 1:
输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
  • 示例 2:

输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)

解题思路:

关于这个问题我们要想怎么让一条船的利益最大化,让船载下尽可能多的人?

让最重的人和最轻的人一起走似乎是一个好办法。

首先我们肯定先要对每个人的体重进行排序,然后计算当下最重的人和最轻的人体重之和并与limit进行比较,如果小于等于,船数加1,除去这两个人计算当下最重的人和最轻的人体重;如果大于,则船数加1,只装最重的人,除去这个人计算当下最重的人继续比较。

相应的代码如下:

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        people.sort()
        size = len(people)
        left, right = 0, size - 1
        ans = 0
        while left < right:
            if people[left] + people[right] > limit:
                right -= 1
            else:
                left += 1
                right -= 1
            ans += 1
        if left == right:
            ans += 1
        return ans

跳跃游戏

题目链接:跳跃游戏

描述:给定一个非负整数数组 nums,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置位于数组的第一个下标处。

要求:判断是否能够到达最后一个下标。

说明

  • 1≤nums.length≤3×104。
  • 0≤nums[i]≤105。

示例

  • 示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再下标 1 跳 3 步到达最后一个下标。
  • 示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

解题思路:

不断地更新能到达的最远位置,最后与数组的长度比较

如果我们能通过前面的某个位置 j,到达后面的某个位置 i,则我们一定能到达区间 [j,i] 中所有的点(j≤i)。

而前面的位置 j 肯定也是通过 j 前面的点到达的。所以我们可以通过贪心算法来计算出所能到达的最远位置。具体步骤如下:

  1. 初始化能到达的最远位置 maxi 为 0。
  2. 遍历数组 nums
  3. 如果能到达当前位置,即 maxi≤i,并且当前位置 + 当前位置最大跳跃长度 > 能到达的最远位置,即 i+nums[i]>maxi,则更新能到达的最远位置 maxi。
  4. 遍历完数组,最后比较能到达的最远位置 maxi 和数组最远距离 size - 1 的关系。如果 maxi>=len(nums),则返回 True,否则返回 False

代码:

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        size = len(nums)
        max_i = 0
        for i in range(size):
            if max_i >= i and i + nums[i] > max_i:
                max_i = i + nums[i]
            
        return max_i >= size - 1

跳跃游戏2

题目链接:跳跃链接

描述:给定一个非负整数数组 nums,数组中每个元素代表在该位置可以跳跃的最大长度。开始位置为数组的第一个下标处。

要求:计算出到达最后一个下标处的最少的跳跃次数。假设你总能到达数组的最后一个下标处。

说明

  • 1≤nums.length≤104。
  • 0≤nums[i]≤1000。

示例

  • 示例 1:

输入:nums = [2,3,1,1,4]
输出:2
解释:跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

解题思路:

对于每一个位置 i 来说,所能跳到的所有位置都可以作为下一个起跳点,为了尽可能使用最少的跳跃次数,所以我们应该使得下一次起跳所能达到的位置尽可能的远。简单来说,就是每次在「可跳范围」内选择可以使下一次跳的更远的位置。这样才能获得最少跳跃次数。具体做法如下:

  1. 定义几个变量:当前所能达到的最远位置 end,下一步所能跳到的最远位置 max_pos,最少跳跃次数 setps
  2. 遍历数组 nums 的前 len(nums) - 1 个元素:
  3. 每次更新第 i 位置下一步所能跳到的最远位置 max_pos
  4. 如果索引 i 到达了 end 边界,则:更新 end 为新的当前位置 max_pos,并令步数 setps 加 1
  5. 最终返回跳跃次数 steps

代码:

class Solution:
    def jump(self, nums: List[int]) -> int:
        end, max_pos = 0, 0
        steps = 0
        for i in range(len(nums) - 1):
            max_pos = max(max_pos, nums[i] + i)
            if i == end:
                end = max_pos
                steps += 1
        return steps

工作原理

  1. 在循环中迭代 nums 数组的每个元素,除了最后一个元素(因为不需要从最后一个元素再跳跃),使用 range(len(nums) - 1) 来遍历。

  2. 对于每个索引 i,更新 max_posi + nums[i] 和当前 max_pos 中的较大值。这表示我们正在检查当前位置 i 能否让我们跳得更远。

  3. 如果当前索引 i 已经等于 end,这意味着我们已经用尽了上一次跳跃所能提供的范围。此时我们需要进行下一次跳跃。因此,我们将 end 更新为 max_pos,表示新的跳跃范围,并增加 steps 来记录这次跳跃。就证明end已经大于等于数组的长度了,即可以到达最后一个下标处了,steps也不需要再增加了。

  4. 当循环结束时,我们已经找到了达到最后一个位置所需的最小跳跃次数,因此返回 steps

如果最后没走到边界也就是最后的end,就证明end已经大于等于数组的长度了,即可以到达最后一个下标处了,steps也不需要再增加了。

用最少数量的箭引爆气球

题目链接:用最少数量的箭引爆气球

描述:在一个坐标系中有许多球形的气球。对于每个气球,给定气球在 x 轴上的开始坐标和结束坐标 (xstart,xend)。

同时,在 x 轴的任意位置都能垂直发出弓箭,假设弓箭发出的坐标就是 x。那么如果有气球满足 xstart≤x≤xend,则该气球就会被引爆,且弓箭可以无限前进,可以将满足上述要求的气球全部引爆。

现在给定一个数组 points,其中 points[i]=[xstart,xend] 代表每个气球的开始坐标和结束坐标。

要求:返回能引爆所有气球的最小弓箭数。

说明

  • 1≤points.length≤10^{5}
  • points[i].length==2。
  • 2^{31}≤xstart<xend≤2^{31}−1。

示例

  • 示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用 2 支箭来爆破:
- 在x = 6 处射出箭,击破气球 [2,8] 和 [1,6]。
- 在x = 11 处发射箭,击破气球 [10,16] 和 [7,12]。
  • 示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要 4 支箭

解题思路:

我们使用贪心算法求最小弓箭数,即让每个弓箭利益最大化,那就让用重叠区域的气球只用一根弓箭即可。那么如何判断气球有没有重叠区域呢?

首先肯定先要对数组进行排序,按结束坐标会简单一点,如果按开始坐标排序,出现下面情况不好判断弓箭的位置:

[0..................6]
   [1..2]    
             [4..5]

 而按结束坐标,取重叠气球结束坐标的最小值即可为弓箭的位置。

然后判断分界的情况,气球间不想重叠怎么办:

初始情况下,第一支弓箭的坐标为第一个区间的结束位置,然后弓箭数为 1。然后依次遍历每段区间。

如果遇到弓箭坐标小于区间起始位置的情况,说明该弓箭不能引爆该区间对应的气球,需要用新的弓箭来射,所以弓箭数加 1,弓箭坐标也需要更新为新区间的结束位置。

最终返回弓箭数目。

代码:

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key=lambda x:x[1])
        res=1
        r=points[0][1]
        for i in range(1,len(points)):
            if points[i][0]>r:
                r=points[i][1]
                res+=1

        return res

 

卡车上的最大单元数

题目链接:卡车上的最大单元数

描述:现在需要将一些箱子装在一辆卡车上。给定一个二维数组 boxTypes,其中 boxTypes[i]=[numberOfBoxesi,numberOfUnitsPerBoxi]。

numberOfBoxesi 是类型 i 的箱子的数量。numberOfUnitsPerBoxi 是类型 i 的每个箱子可以装载的单元数量。

再给定一个整数 truckSize 表示一辆卡车上可以装载箱子的最大数量。只要箱子数量不超过 truckSize,你就可以选择任意箱子装到卡车上。

要求:返回卡车可以装载的最大单元数量。

说明

  • 1≤boxTypes.length≤1000。
  • 1≤numberOfBoxesi,numberOfUnitsPerBoxi≤1000。
  • 1≤truckSize≤106。

示例

  • 示例 1:
输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4
输出:8
解释
箱子的情况如下:
- 1 个第一类的箱子,里面含 3 个单元。
- 2 个第二类的箱子,每个里面含 2 个单元。
- 3 个第三类的箱子,每个里面含 1 个单元。
可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。
单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8
解题思路:

题目中,一辆卡车上可以装载箱子的最大数量是固定的(truckSize),那么如果想要使卡车上装载的单元数量最大,就应该优先选取装载单元数量多的箱子。

所以,从贪心算法的角度来考虑,我们首先应该按照每个箱子可以装载的单元数量对数组 boxTypes 从大到小排序。然后优先选取装载单元数量多的箱子。

代码:

class Solution:
    def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
        boxTypes.sort(key=lambda x:x[1], reverse=True)
        sums=0
        
        if truckSize==0:
            return 0
        for i in range(len(boxTypes)):
            while boxTypes[i][0]>0 and truckSize>0:
                truckSize-=1
                boxTypes[i][0]-=1
                sums+=boxTypes[i][1]
                if truckSize==0:
                    return sums
                if truckSize>0 and i == len(boxTypes)-1 and boxTypes[i][0]==0:
                    return sums
                if boxTypes[i][0]==0:
                    break

这里还要注意的边界条件是当前已经是最后一个箱子,但是卡车还可以装载时,依旧要返回sums

if truckSize>0 and i == len(boxTypes)-1 and boxTypes[i][0]==0:
                    return sums

如果不加这个,出现上述情况时会返回空值。

我们也可以对其进行简化:

class Solution:
    def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
        boxTypes.sort(key=lambda x:x[1], reverse=True)
        res = 0
        for box in boxTypes:
            if truckSize > box[0]:
                res += box[0] * box[1]
                truckSize -= box[0]
            else:
                res += truckSize * box[1]
                break
        return res

这样就不用逐个判断装载了,因为大的箱子如果能装下就一定会装,所以只需要判断是全装还是部分装就行了,甚至不需要判断truckSize是否大于0,因为等于0后对结果没有影响。


那我们今天的学习就到这里,我们下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2171342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目实战:k8s部署考试系统

一、新建nfs服务器&#xff08;192.168.1.44&#xff09; 1.基础配置&#xff08;IP地址防火墙等&#xff09; 2.配置时间同步 [rootlocalhost ~]# yum -y install ntpdate.x86_64 [rootlocalhost ~]# ntpdate time2.aliyun.com 27 Sep 10:28:08 ntpdate[1634]: adjust tim…

风景视频素材高清无水印怎么找?推荐几个风景视频素材下载网站

那些绝美的大自然风景视频素材都在哪里找的&#xff1f;今天就跟大家聊聊那些可以下载高清无水印的大自然风景视频素材的网站&#xff0c;如果你也在苦苦寻找&#xff0c;快来看看吧&#xff5e; 1. 稻虎网 稻虎网作为国内遥遥领先的视频素材网站&#xff0c;提供了大量高清无…

2024最新Linux Socket编程

我们深谙信息交流的价值&#xff0c;那网络中进程之间如何通信&#xff0c;如我们每天打开浏览器浏览网页时&#xff0c;浏览器的进程怎么与web服务器通信的&#xff1f;当你用QQ聊天时&#xff0c;QQ进程怎么与服务器或你好友所在的QQ进程通信&#xff1f;这些都得靠socket&am…

Meta广告资料库使用教程:Facebook、Instagram海外社媒营销统统拿下!

社交媒体现今已成为大部分商家宣传推广方案中的重要工具&#xff0c;尤其是Meta旗下的两个主流社媒平台Facebook和Instagram&#xff0c;活跃用户数量以数十亿计&#xff0c;分布地区也非常广。要运用好自带影响力的社媒平台的传播力量和庞大数据&#xff0c;优化自身的宣传推广…

【Kubernetes】常见面试题汇总(四十二)

目录 96. Kubernetes 体系结构有哪些不同的组成部分&#xff1f; 97.您能否简要介绍一下 Kubernetes 中主节点的工作&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二…

亚马逊 Bedrock 平台也能使用Llama 3.2 模型了

亚马逊 Bedrock 平台推出 Llama 3.2 模型&#xff1a;多模态视觉和轻量级模型 概述 由 Meta 提供的最新 Llama 3.2 模型现已在 Amazon Bedrock 平台上推出。这一新模型系列标志着 Meta 在大型语言模型&#xff08;LLM&#xff09;领域的最新进展&#xff0c;它在多种应用场景…

养猫久了才发现,宠物空气净化器才是真正除猫毛的神器

相信每个打工人都在期待这个国庆小长假吧&#xff0c;终于等到了&#xff01;这次我要把属于我的都夺回来&#xff01;刚好工资到手、小长假我有&#xff0c;只想往家里一躺什么也不想&#xff0c;唯一最想做的就是要在这个节假日里好好的陪一下我家猫咪&#xff0c;还有就是买…

【企业微信】群机器人自动消息配置

0、群聊机器人 内部企微群聊可以添加一个机器人&#xff0c;这个机器人其实是个消息接口&#xff0c;可以外部脚本来自动定时发送消息到群里&#xff0c;打工人最有用的提醒就是每周提醒发周报了。 1、创建机器人 一般公司都没有人使用&#xff0c;我们可以手动创建一个。 …

前端练习总结(1)

前端实习练习题 前端实习笔试题0920 visibility:hidden display:none把鼠标移到按钮并点击时 hover active focus的顺序代码输出结果1代码输出结果2CSS中哪些属性可以继承cookie sessionStorage localstorage区别面向对象基本特征有哪些,请具体说明下列关于v-model的说法,哪项…

统信服务器操作系统进入【单用户模式】

统信服务器操作系统D版、E版、A版进入单用户模式的方式。 文章目录 前言一、问题现象二、问题原因三、解决方案1. D版问题解决方案2. E版及A版问题解决方案前言 D版又称企业版、E版又称欧拉版、A版又称龙蜥版。 单用户模式主要是在 grub2 引导时编辑内核引导,一般用于修改用…

STM32CUBEIDE FreeRTOS操作教程(五):mutex互斥信号量

STM32CUBEIDE FreeRTOS操作教程&#xff08;五&#xff09;&#xff1a;mutex互斥信号量 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件&#xff0c;不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例&am…

个人网站介绍和部署(开源)

前言&#xff1a; 大家好&#xff0c;我是神的孩子都在歌唱&#xff0c;这是我csdn的博客 , 这是我做的一个神唱网站项目&#xff0c;专门是为了满足自己的需求写的&#xff0c;需要什么就做什么&#xff0c;代码完全开源github&#xff0c;含有安装部署教程&#xff0c;此项目…

Geekbench6使用指南:Linux系统性能测试,如此简单!

在当今计算机性能日益重要的时代&#xff0c;Geekbench 成为了测试 CPU 性能的热门工具。本文将带你深入了解如何使用 Geekbench&#xff0c;让你的电脑性能一目了然。 1. 什么是 Geekbench&#xff1f; Geekbench 是一款跨平台的基准测试工具&#xff0c;能够评估单核和多核…

避免glibc版本而报错,CentOS等Linux安装node.js完美方法

概述 对于Node.js v18.x或更高&#xff0c;Node.js官方默认是在Ubuntu 20.04, Debian 10, RHEL 8,CentOS 8等高版操作系统上编译得到的&#xff0c;高版本操作系统的glibc版本≥2.28。所以&#xff0c;下载Node.js后&#xff0c;也需要glibc版本≥2.28才能使用。 而CentOS 7.x等…

【YOLOv10改进[SPPF]】使用 v9的SPPELAN替换SPPF模块 + 含全部代码和详细修改方式

本文将进行在YOLOv10中使用SPPELAN改进v10 的实践,助力YOLOv10目标检测效果,文中含全部代码、详细修改方式。助您轻松理解改进的方法。 改进前和改进后的参数对比如下: 目录 一 YOLOV9 二 使用SPPELAN改进v10的实践 1 整体修改 ① 添加SPPELAN.py文件

入选ECCV 2024!覆盖5.4w+图像,MIT提出医学图像分割通用模型ScribblePrompt,性能优于SAM

外行看热闹&#xff0c;内行看门道&#xff0c;这句话在医学影像领域可谓是绝对真理。不仅如此&#xff0c;即便身为内行人&#xff0c;要想在复杂的 X 光片、CT 光片或 MRI 等医学影像上准确看出些「门道」来&#xff0c;也并非易事。而医学图像分割则是通过将复杂的医学图像中…

双十一有什么推荐好物?,这些你不能错过的宝藏好物推荐

随着双十一的临近&#xff0c;这场盛大的购物狂欢蓄势待发&#xff01;为了让大家不在琳琅满目的商品中徘徊&#xff0c;琪琪用心归纳了一份购物清单&#xff0c;分享那些我亲自使用过&#xff0c;觉得必须拥有的商品。 这些商品不仅价格公道&#xff0c;而且质量上乘&#xf…

如何在iPad上用Chrome实现无痕浏览

在数字化时代&#xff0c;隐私保护已成为我们日常生活中不可忽视的一部分。特别是在使用移动设备浏览网页时&#xff0c;如何确保个人信息的安全显得尤为重要。本文将详细介绍如何在iPad上使用Chrome浏览器实现无痕浏览&#xff0c;以保护您的在线隐私。 &#xff08;本文由ht…

SpringBoot使用validation进行自参数校验

一&#xff1a;介绍 在 SpringBoot 项目开发中&#xff0c;很多与数据库交互的参数需要校验数据正确性。很多小伙伴会把参数判断写进代码里&#xff0c;但是这种写法往往会有低可读性以及多处使用的时候&#xff0c;需要变更验证规则时&#xff0c;不易于维护等缺点。今天给大家…

数据工程师岗位常见面试问题-1(附回答)

数据工程师已成为科技行业最重要的角色之一&#xff0c;是组织构建数据基础设施的骨干。随着企业越来越依赖数据驱动的决策&#xff0c;对成熟数据工程师的需求会不断上升。如果您正在准备数据工程师面试&#xff0c;那么应该掌握常见的数据工程师面试问题&#xff1a;包括工作…