代码随想录算法训练营Day34 | 1005. K次取反后最大化的数组和 | 134. 加油站 | 135. 分发糖果

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

题目链接 | 解题思路

本题非常简单,但如果要以贪心的思想高效解题,还要分成两种情况思考:

  • 如果数组中有负数
    • 局部最优:优先反转绝对值最大的负数
  • 如果数组中全部非负,此时还需要翻转
    • 局部最优:优先反转最小的非负数

这样的解法复杂度非常低,堪称最优解。

本题最重要的是保持贪心的思想来解题,即使题目很简单,但需要分类的贪心尚且是第一次遇到,要想清楚局部最优再开始解决贪心问题,否则就无法获得贪心的锻炼。

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

直接的解法

每一次取反,都找到当前的最小值然后进行取反,进行 k 次即可。
但这么写的复杂度很高,每次都需要遍历整个数组,不推荐,而且似乎没有体现出贪心的思想。

class Solution:
    def findMinIdx(self, nums: List[int]) -> int:
        min_idx = -1
        min_value = float('inf')
        for i in range(len(nums)):
            if nums[i] < min_value:
                min_idx = i
                min_value = nums[i]
        return min_idx

    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        for _ in range(k):
            curr_min_idx = self.findMinIdx(nums)
            nums[curr_min_idx] *= -1
        return sum(nums)

134. 加油站

题目链接 | 解题思路

如果 总油量 - 总消耗 = 总剩余油量 ≥ \geq 0,那就一定能找到一个起始位置能成功环绕一圈。
那么从头开始,记录 [0, curr_pos] 区间内的总剩余油量。一旦总剩余油量变为负数,意味着从 [0, curr_pos] 中的任一位置出发都会面临断油,所以将 curr_pos + 1 作为新的区间起点,重新记录区间内的总剩余油量。
一旦总剩余油量变为负数,意味着从 [0, curr_pos] 中的任一位置出发都会面临断油可以从下图中得到验证:

局部最优:当前的区间剩余油量若是负数,则从其中任意位置出发都会断油,只有从后面的位置出发才有可能成功环绕。
全局最优:找到起始位置。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        if sum(gas) < sum(cost):
            return -1
        
        curr_rest = 0
        curr_start = 0
        for i in range(len(gas)):
            curr_rest += gas[i] - cost[i]
            if curr_rest < 0:
                curr_rest = 0
                curr_start = i + 1
        return curr_start

暴力算法

复杂度为 O ( n 2 ) O(n^2) O(n2) 的暴力算法,提交的话会超时。
暴力算法的写法比较 tricky,模拟一圈非常依赖 % 以及 while loop 的使用,要想写对也需要很正确的思路。

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        for start in range(len(gas)):
            curr_gas = gas[start] - cost[start]
            curr_pos = (start + 1) % len(gas)
            while curr_gas > 0 and curr_pos != start:
                curr_gas += gas[curr_pos] - cost[curr_pos]
                curr_pos = (curr_pos + 1) % len(gas)
            if curr_gas >= 0 and curr_pos == start:
                return start
        return -1

135. 分发糖果

题目链接 | 解题思路

本题要求每个孩子至少分配到 1 个糖果相邻的孩子中,评分高的孩子必须获得更多的糖果,其中第二个要求需要从左向右和从右向左的比较均满足条件。

重点:先确定一边,再确定另一边。两个方向一起考虑只会顾此失彼。
这也代表着需要两个情况下的局部最优。

  • 右孩子的评分大于左孩子:

    • 局部最优为,如果右边的评分比左边的高,则右边就比左边多一个糖果
    • 全局最优为,相邻的孩子中,评分高的右孩子比左孩子的糖果更多
    • 此时需要从左向右遍历,因为这个局部最优是依赖左侧已经发生的更新。
      在这里插入图片描述
  • 左孩子的评分大于右孩子:

    • 局部最优为,如果左边的评分比右边的高,则左边就比右边多一个糖果,同时也要保证之前维持的全局最优相邻的孩子中,评分高的右孩子比左孩子的糖果更多
    • 全局最优为,相邻的孩子中,评分高的孩子获得更多的糖果
    • 此时需要从右向左遍历,因为这个局部最优依赖于右侧已经发生的更新。
      在这里插入图片描述
class Solution:
    def candy(self, ratings: List[int]) -> int:
        candies = [1] * len(ratings)

        for i in range(1, len(ratings)):
            if ratings[i] > ratings[i-1]:
                candies[i] = candies[i-1] + 1
            
        for j in range(len(ratings) - 2, -1, -1):
            if ratings[j] > ratings[j+1]:
                candies[j] = max(candies[j], candies[j+1] + 1)
        
        return sum(candies)

重点:有双向要求,就要拆成双向贪心!要分别考虑不同的局部最优,分步实现贪心算法!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值