• 贪心算法(一) | leecode刷题笔记


    跟随carl代码随想录刷题
    语言:python


    贪心

    选择每一阶段的局部最优,从而达到全局最优。

    如:每次都拿走面额最大的钱,最终就可以拿到最多的钱。

    什么时候用贪心?

    举反例!如果想不到反例,那就试一试贪心。

    贪心有时是常识性的推导,会认为本就应该这么做。

    贪心的解题步骤

    将问题分为若干子问题
    找出适合的贪心策略
    求解每一个子问题的最优解
    将局部最优解堆叠成全局最优解

    455. 简单分发饼干

    题目:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干
    每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
    👉示例 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.

    题目分析

    思路1:

    • 小饼干先喂饱小胃口的

    思路2:

    • 大饼干既可以满足胃口小的也可以满足胃口大的,那局部最优就是:充分利用饼干尺寸喂饱一个。
    • 全局最优:喂饱尽可能多的小孩。
    • index控制饼干数组的遍历。
      遍历饼干采用自减的方式。
      在这里插入图片描述

    完整代码如下

    小饼干先喂饱小胃口

    class Solution:
        def findContentChildren(self, g: List[int], s: List[int]) -> int:
            g.sort()
            s.sort()
            res = 0
            for i in range(len(s)):
                if res<len(g) and s[i] >= g[res]: # 小饼干先喂饱小胃口
                    res += 1
            return res
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    大饼干先喂饱大胃口

    class Solution:
        def findContentChildren(self, g: List[int], s: List[int]) -> int:
            g.sort()
            s.sort()
            res = 0
            start = len(s) - 1
            for i in range(len(g)-1, -1, -1):
                if start >= 0 and s[start] >= g[i]: # 大饼干先喂饱大胃口
                    res += 1
                    start -= 1
            return res
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    860. 简单柠檬水找零

    题目:在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
    每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
    注意,一开始你手头没有任何零钱。
    给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账如果你能给每位顾客正确找零,返回 true ,否则返回 false
    👉示例 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。

    题目分析

    把能遇到的情况分析一下:
    10美元可以给20找零
    5美元可以给1020找零。所以,5美元更万能。

    完整代码如下

    class Solution:
        def lemonadeChange(self, bills: List[int]) -> bool:
            five_ = 0
            ten_ = 0
            twenty_ = 0
            
            for i in range(len(bills)):
                if bills[i] == 5:  # 如果顾客付的是5块
                    five_ += 1  # 直接收
                elif bills[i] == 10:  # 如果顾客付的是10块
                    if five_ > 0:  # 把10块收起来,并找一个5块
                        ten_ += 1
                        five_ -= 1
                    else:
                        return False
                elif bills[i] == 20:  # 如果顾客付的是20块
                    if ten_ > 0 and five_ > 0:  # 如果有5块也有10块,优先找1个10块、1个五块,并把20块收起来
                        ten_ -= 1
                        five_ -= 1
                        twenty_ += 1
                    elif five_ > 2:  # 否则,如果有五块,那就找3个五块
                        five_ -= 3
                        twenty_ += 1
                    else:
                        return False
            return True
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    1005. 简单K 次取反后最大化的数组和

    题目:给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:
    选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
    重复这个过程恰好 k 次。可以多次选择同一个下标 i 。
    以这种方式修改数组后,返回数组 可能的最大和 。
    👉示例 1:
    输入:nums = [4,2,3], k = 1
    输出:5
    解释:选择下标 1 ,nums 变为 [4,-2,3] 。
    👉示例 2:
    输入:nums = [3,-1,0,2], k = 3
    输出:6
    解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
    👉示例 3:
    输入:nums = [2,-3,-1,5,-4], k = 2
    输出:13
    解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

    题目分析

    局部最优:每次都只改变最小值的符号
    全局最优:这样就可以得到最大值。

    • nums按绝对值从大到小排列
      A = sorted(nums, key=abs, reverse=True)

    完整代码如下

    我的代码

    我自己写出来的呢😄
    请添加图片描述

    class Solution:
        def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
            while k>0:
                nums.sort()
                nums[0] = -nums[0]
                k -= 1
        
            sum_ = 0
            for i in range(len(nums)):
                sum_ += nums[i]            
            return sum_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    数组元素求和可以直接sum(数组名)

    class Solution:
        def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
            while k>0:
                nums.sort()
                nums[0] = -nums[0]
                k -= 1
    
            return sum(nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    carl的代码

    请添加图片描述


    请添加图片描述

    class Solution:
        def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
            # 将nums按绝对值从大到小排列
            A = sorted(nums, key=abs, reverse=True)  
    
            for i in range(len(A)):
                if k > 0 and A[i] < 0:
                    A[i] = -A[i]
                    k -= 1
            if k > 0:
                A[-1] *= (-1)**k
            
            return sum(A)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    carl的代码优化后

    最后K的判断,只需用判断K的奇偶即可。如果是奇数,那A[-1] = -A[1],否则不操作。

    class Solution:
        def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
            # 将nums按绝对值从大到小排列
            A = sorted(nums, key=abs, reverse=True)  
    
            for i in range(len(A)):
                if k > 0 and A[i] < 0:
                    A[i] = -A[i]
                    k -= 1
            if k > 0:
                if k % 2 == 1:
                    A[-1] = - A[-1]
            
            return sum(A)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    376. 中等摆动序列

    题目:如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
    例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
    相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
    子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
    给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

    题目分析

    题目要求:返回nums中作为 摆动序列最长子序列的长度
    思路:转化为求数列中的拐点数目

    • 所谓拐点,即当前差值curC = nums[i] - nums[i-1]与前一个差值preC = nums[i-1] - nums[i-2]是否异号,即:乘积是否<0
      • 注意:因为初始化preC = 0,所以判断条件应改为乘积是否 <= 0 and 当前差值不为0

    完整代码如下

    class Solution:
        def wiggleMaxLength(self, nums: List[int]) -> int:
            preC, curC, res = 0, 0, 1
            for i in range(len(nums)-1):
                curC = nums[i + 1] - nums[i]
                # 当前差值 与 前一个差值 异号,并且当前差值不为0时
                if curC * preC <= 0 and curC != 0:  # 差值为0时不算摆动。
                    res += 1  # 则拐点数量+1
                    preC = curC  # 向后遍历,把当前差值 作为 前一个差值
            return res  # 返回拐点数量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    53. 最大子数组和

    题目:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
    子数组 是数组中的一个连续部分。
    👉示例 1:
    输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
    输出:6
    解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
    👉示例 2:
    输入:nums = [1]
    输出:1
    👉示例 3:
    输入:nums = [5,4,-1,7,8]
    输出:23

    题目分析

    • 连续和为负数的时候,立刻放弃。因为负数只会拉低总和。因此

      • for i in range(len(nums)): # 遍历数组
        • count += nums[i] # 计数
        • 用count统计局部最小值。如果当前连续和大于目前的结果result:count > result,就更新result:result = count
        • 当从nums[i]开始计数的连续和为负数时count < 0,放弃此次计数:count = 0,然后从下一个元素numd[i+1]重新计算连续和
    • 全局最优:选取最大连续和。return result

    • 思路陷阱:

      • 放弃的条件为:连续和为负数
      • 而不是:连续和加上负数
        在这里插入图片描述

    完整代码如下

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            result = -float('inf')
            count = 0
            for i in range(len(nums)):
                count += nums[i]
                if count > result:  # 如果count比result大,就更新result
                    result = count
                if count <= 0:  # `<=`或`<`都可以。`连续和`为负数,直接放弃,开启下一个元素的计数
                    count = 0
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    122. 中等买卖股票的最佳时机 II

    题目:给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格
    在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
    返回 你能获得的 最大 利润
    👉示例 1:
    输入:prices = [7,1,5,3,6,4]
    输出:7
    解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4
    随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3
    总利润为 4 + 3 = 7
    👉示例 2:
    输入:prices = [1,2,3,4,5]
    输出:4
    解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4
    总利润为 4
    👉示例 3:
    输入:prices = [7,6,4,3,1]
    输出:0
    解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0

    题目分析

    价格数组 -> 利润数组 -> 贪心法则:每天只收取正利润

    局部最优:收集每天的正利润
    全局最优:得到最大利润

    请添加图片描述

    完整代码如下

    class Solution:
        def maxProfit(self, prices: List[int]) -> int:
            count = 0
            for i in range(len(prices)-1):
                count += max(prices[i+1] - prices[i], 0)
            return count
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    714. 中等买卖股票的最佳时机含手续费

    题目:给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格整数 fee 代表了交易股票的手续费用
    你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
    返回获得利润的最大值
    注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
    👉示例 1:
    输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
    输出:8
    解释:能够达到的最大利润:
    在此处买入 prices[0] = 1
    在此处卖出 prices[3] = 8
    在此处买入 prices[4] = 4
    在此处卖出 prices[5] = 9
    总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
    👉示例 2:
    输入:prices = [1,3,7,5,10,3], fee = 3
    输出:6

    题目分析

    完整代码如下

    class Solution:
        def maxProfit(self, prices: List[int], fee: int) -> int:
            result = 0
            minPrice = prices[0]
            for i in range(1, len(prices)):
                if prices[i] < minPrice:
                    minPrice = prices[i]
                elif prices[i] >= minPrice and prices[i] <= minPrice + fee: 
                    continue
                else: 
                    result += prices[i] - minPrice - fee
                    minPrice = prices[i] - fee
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    idea 启动安卓项目,模拟器点击无反应
    CSS学习笔记
    java计算机毕业设计springboot+vue在线选课系统
    Spring Boot 国际化踩坑指南
    如何使用现有工具三分钟之内从无到有设计一款滤波器?
    推荐一个在线ide的网站
    Spring核心与设计思想
    机器学习 | MATLAB实现支持向量机回归参数设定
    GPLV2协议重点F&Q整理与总结
    JMETER前置处理器类型
  • 原文地址:https://blog.csdn.net/qq_44250700/article/details/126334148