LeetCode---贪心算法

贪心算法

参考公众号:代码随想录。

什么是贪心

贪心的本质是选择每个阶段的局部最优,从而达到全局最优。

例如,有堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?指定每次拿最大的,最终结果就是拿走最大数额的钱。每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。

再例如:如果有一堆盒子,有一个背包体积为n,如何把背包尽可能装满,如果还每次选最大的盒子,就不行了,这时候就需要动态规划。

什么时候用贪心

贪心算法并没有固定的套路,所以难点就是如何通过局部最优,推出整体最优。

严重是否可以用贪心:最好的策略就是举反例,如果想不到反例,那么就试试贪心吧。

贪心的一般解题步骤

贪心算法一般分为四步:

  1. 将问题分解为若干个子问题;
  2. 找出适合的贪心策略;
  3. 求解每个子问题的最优解;
  4. 将局部最优解堆叠成全局最优解。

LeetCode

455. 分发饼干

题目链接

思路

大尺寸的饼干既可以满足胃口小的孩子也可以满足胃口大的孩子,那么就应该优先满足胃口大的。
这个的局部最优就是将大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的孩子。
可以尝试使用贪心策略,先将饼干数组和孩子数组排序。
然后从后向前遍历孩子数组,大饼干优先满足胃口大的,并统计满足的孩子数量。

解法1

# 时间复杂度:主要是排序的时间复杂度O(mlogm+nlogn)
# 空间复杂度:O(logm+logn),其中 m 和 n 分别是数组 g 和 s 的长度。空间复杂度主要是排序的额外空间开销。

def findContentChildren(g, s):
    # 1. 先对g和s升序排序
    g.sort()
    s.sort()

    # 2. 因为孩子胃口和饼干都已经排过序,所以从小到大选择每个孩子能满足每个孩子胃口的最小饼干(贪心算法)
    i = 0
    j = 0
    while i < len(g) and j < len(s):
        if g[i] <= s[j]:
            i += 1
            j += 1
        else:
            j += 1

    return i


if __name__ == '__main__':
    g = [1, 2, 3]
    s = [1, 1]
    print(findContentChildren(g, s))

376. 摆动序列

题目链接

思路

  1. 假如有2个数,计算出其差值,假设为正数;
  2. 假入有3个数,计算nums[2]-nums[1]差值,是否为负数,这样就可以达到局部最优;
  3. 如果不满足则计算下一组数字,忽略当前数字。

例如: nums= [10, 8, 6, 8], 第一组8-10=-2,第二组6-8=-2,第三组8-6=2,则存在[10,6,8]的子序列满足正负交替。注意:第一组数字[10,8]和[10,6]都可以满足,但是如果选[10,8]第3位再选6或者8就不满足条件了。

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        length = len(nums)
        if length <= 1:
            return length

        # 初始化
        max_length = 1
        pre_diff = 0
        # result = [nums[0]]

        for i in range(1, len(nums)):
            diff = nums[i] - nums[i-1]
            # 如果当前节点和前一个节点差值与前一组数分别为“峰”或“谷”,则满足要求
            if (diff > 0 and pre_diff <= 0) or (diff < 0 and pre_diff >= 0):
                max_length += 1
                pre_diff = diff
                # result.append(nums[i])

        return max_length

53. 最大子序和

题目链接

思路

如果 [-2,1] 在一起,计算起点的时候,是从1开始计算,因为负数只会拉低总和,这就是贪心的地方!
局部最优:当前“连续和”为负数的时候立刻放弃,从下个元素重新计算“连续和”,因为负数加上下个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”,局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。

从代码⻆度上来讲:遍历nums,从头开始用temp_sum累积,如果temp_sum 加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积temp_sum 了,因为已经变为负数的temp_sum,只会拖累总和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        """
        1. result 记录当前遍历过的序列的最大和
        2. temp_sum 在result的基础上继续遍历,如果遇到temp_sum>result就更新result
        :param nums:
        :return:
        """

        result = float("-inf")
        temp_sum = 0

        for i in range(len(nums)):
            # 计算临时和
            temp_sum += nums[i]
            # temp_sum 大于 result时更新result,相当于不断确定最大序终止位置
            if temp_sum > result:
                result = temp_sum
            # temp_sum 小于0时重置为0,相当于重置最大子序起始位置,因为负数只会让和更小,所以从下一个数重新开始计算
            if temp_sum < 0:
                temp_sum = 0

        return result

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

题目链接

解法1: 贪心

def maxProfit(prices):
    """
    1. profit记录利润
    2. start购买的价格,end记录售出的价格
    3. 遍历 prices,当nums[i]小于nums[i-1]时,售出股票,并重置start和end为nums[i]
    4. 挡nums[i]>=nums[i-1]时,说明还在上涨,则end=nums[i]
    :param prices:
    :return:
    """
    profit = 0
    start = prices[0]
    end = prices[0]

    for i in range(1, len(prices)):
        if prices[i] < prices[i-1]:
            profit += end - start
            start = prices[i]

        end = prices[i]
        # 遍历到最后一个元素时,要结算收益
        if i == len(prices) - 1 and (end - start) > 0:
            profit += end - start

    return profit

更优思路:

def max_profit(prices: List[int]) -> int:
    result = 0
    i = 0
    while i < len(prices)-1:
        # 1. 如果后一天和前一天是上升趋势,那么就取其差值为利润,相当于今天买入,明天卖出,
        # 这样一直累加则最终结果就是最大利润,例如: [1,3,5], (3-1) + (5-3)=4 ;
        # 2. 如果遇到下降趋势则不做任何操作,相当于不买入也不卖出
        if prices[i + 1] > prices[i]:
            result += prices[i + 1] - prices[i]
        i += 1

    return result

55. 跳跃游戏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值