贪心算法(一) | 简单题目、摆动序列、最大子数组和、股票利润 | 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

大饼干先喂饱大胃口

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

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

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_
数组元素求和可以直接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)

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)

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)

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  # 返回拐点数量

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

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

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值