代码随想录算法训练营第二十八天 |贪心算法part02

179 阅读5分钟

代码随想录算法训练营第二十八天 |贪心算法part02

122 买卖股票的最佳时机II

image.png

如果想到其实最终利润是可以分解的,那么本题就很容易了!

如何分解呢?

假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。

相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。

此时就是把利润分解为每天为单位的维度,而不是从 0 天到第 3 天整体去考虑!

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

    prices = [7,1,5,3,6,4]
    max_ = 0
    result = []
    for i in range(len(prices)-1):
        diff = prices[i+1] - prices[i]
        result.append(diff)
    sum_ = 0
    for i in result:
        if i>0:
            sum_ += i
    print(sum_)
    result = 0
    for i in range(1, len(prices)):
        result += max(prices[i] - prices[i - 1], 0)
    return result

55 跳跃游戏

image.png

当前位置元素如果是 3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?

其实跳几步无所谓,关键在于可跳的覆盖范围!

不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。

这个范围内,别管是怎么跳的,反正一定可以跳过来。

那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!

每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。

贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围) 整体最优解:最后得到整体最大覆盖范围,看是否能到终点

局部最优推出全局最优,找不出反例,试试贪心!

    if len(nums) == 1:
        return True
    cover = 0
    i = 0
    while i <= cover: # cover是一直变化的,所以如果用for来确定i的上界,不合适。cover代表着i的上界
        cover = max(nums[i] + i ,cover)
        if cover >= len(nums)-1:
            return True
        i += 1
    return False

基于当前最远可到达位置判断

    ## 基于当前最远可到达位置判断
    class Solution:
        def canJump(self, nums: List[int]) -> bool:
            far = nums[0]
            for i in range(len(nums)):
                # 要考虑两个情况
                # 1. i <= far - 表示 当前位置i 可以到达
                # 2. i > far - 表示 当前位置i 无法到达
                if i > far:
                    return False
                far = max(far, nums[i]+i)
            # 如果循环正常结束,表示最后一个位置也可以到达,否则会在中途直接退出
            # 关键点在于,要想明白其实列表中的每个位置都是需要验证能否到达的
            return True

45 跳跃游戏II

image.png

真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!

这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖

    class Solution:
        def jump(self, nums):
            if len(nums) == 1:
                return 0
            
            cur_distance = 0  # 当前覆盖最远距离下标
            ans = 0  # 记录走的最大步数
            next_distance = 0  # 下一步覆盖最远距离下标
            
            for i in range(len(nums)):
                next_distance = max(nums[i] + i, next_distance)  # 更新下一步覆盖最远距离下标
                if i == cur_distance:  # 遇到当前覆盖最远距离下标
                    ans += 1  # 需要走下一步
                    cur_distance = next_distance  # 更新当前覆盖最远距离下标(相当于加油了)
                    if next_distance >= len(nums) - 1:  # 当前覆盖最远距离达到数组末尾,不用再做ans++操作,直接结束
                        break
            
            return ans
    if len(nums)==1:  # 如果数组只有一个元素,不需要跳跃,步数为0
        return 0
    ​
    i = 0  # 当前位置
    count = 0  # 步数计数器
    cover = 0  # 当前能够覆盖的最远距离while i <= cover:  # 当前位置小于等于当前能够覆盖的最远距离时循环
        for i in range(i, cover+1):  # 遍历从当前位置到当前能够覆盖的最远距离之间的所有位置
            cover = max(nums[i]+i, cover)  # 更新当前能够覆盖的最远距离
            if cover >= len(nums)-1:  # 如果当前能够覆盖的最远距离达到或超过数组的最后一个位置,直接返回步数+1
                return count+1
        count += 1  # 每一轮遍历结束后,步数+1

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

image.png

暴力解法:

for i in range(k):
    number = min(nums)
    idx = nums.index(number)
    nums[idx] = -nums[idx]
return sum(nums)

贪心的思路,

局部最优:让绝对值大的负数变为正数,当前数值达到最大,

整体最优:整个数组和达到最大。

那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。

那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

nums.sort(key = lambda x:abs(x),reverse = True)
for i in range(len(nums)):
    if k > 0 and nums[i] < 0 :
        nums[i] *= -1
        k -= 1
if k % 2 == 1:
    nums[-1] *= -1
return sum(nums)