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

95 阅读3分钟

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

代码随想录文章讲解

贪心算法

  • 贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。局部最优可以推出全局最优。
  • 那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
class Solution:
    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        # sort nums by absolute value of element descendingly
        sorted_list = sorted(nums, key=abs, reverse=True)
        for i in range(len(sorted_list)):
            if k > 0 and sorted_list[i] < 0:
                sorted_list[i] *= -1
                k -= 1
                
        if k > 0:
            sorted_list[-1] *= (-1) ** k
        
        return sum(sorted_list)

134. 加油站

代码随想录文章讲解

贪心

  • 首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈
  • i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum。(从i+1的位置开始寻找下一个rest大于0的index)
  • 那么局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        remaining_gas = [ gas[i] - cost[i] for i in range(len(gas)) ]
        if sum(remaining_gas) < 0:
            return -1
        
        cur_sum = 0
        start = 0
        for i in range(len(gas)):
            cur_sum += remaining_gas[i]
            if cur_sum < 0:
                cur_sum = 0
                start = i + 1
        return start

135. 分发糖果

代码随想录文章讲解

贪心

  • 这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。先确定右边评分大于左边的情况(也就是从前向后遍历)

    • 此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
    • 局部最优可以推出全局最优。
  • 再确定左孩子大于右孩子的情况(从后向前遍历)

    • 因为如果从前向后遍历,根据 ratings[i + 1] 来确定 ratings[i] 对应的糖果,那么每次都不能利用上前一次的比较结果了。所以确定左孩子大于右孩子的情况一定要从后向前遍历!
    • 如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
    • 那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量即大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
    • 局部最优可以推出全局最优。
  • 那么本题我采用了两次贪心的策略:

    • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
    • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。

    这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。

class Solution:
    def candy(self, ratings: List[int]) -> int:
        candy_res = [1] * len(ratings)
        
        # right > left
        for i in range(len(ratings) - 1):
            if ratings[i] < ratings[i+1]:
                candy_res[i+1] = candy_res[i] + 1
        
        # right < left
        for i in range(len(ratings) - 2, -1, -1):
            if ratings[i] > ratings[i+1]:
                # have to meet two constraints
                candy_res[i] = max(candy_res[i+1] + 1, candy_res[i])
        
        return sum(candy_res)