算法刷题打卡029 | 贪心算法3

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

题目链接:1005. K 次取反后最大化的数组和 - 力扣(Leetcode)

 看到题目的第一反应以为数组范围都是非负数,于是可以根据k的奇偶性,k为偶数时只对同一个数反复取反,直接返回数组和,k为奇数时返回数组和与最小值的差值。但数据的数据范围包含负数,要最大化数组和,就需要优先将负数取反,负数取反完成后,如果k还有剩余的次数且为奇数,就将绝对值最小的数取反。本质上贪心就是在让数组中的负数尽可能少:

class Solution:
    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        nums.sort(key=lambda x: -abs(x))  # 按绝对值从大到小排序,优先取反绝对值大的负数
        for i in range(len(nums)):
            if k > 0:
                if nums[i] < 0:
                    nums[i] = -nums[i]
                    k -= 1
            else:
                break
        if k % 2 == 1:
            return sum(nums) - 2 * nums[-1]
        else:
            return sum(nums)

LeetCode 134. 加油站

题目链接:134. 加油站 - 力扣(Leetcode)

 暴力解法是模拟从每一个加油站出发,走一圈下来能否实现剩余油量大于0。循环取下标时有些小技巧:

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        if sum(gas) < sum(cost):
            return -1
        n = len(gas)
        # 暴力
        for i in range(n):
            rest = gas[i] - cost[i]
            index = (i + 1) % n
            while rest > 0 and index != i:
                rest += gas[index] - cost[index]
                index = (index + 1) % n
            if rest >= 0 and index == i:
                return i
        return -1

两重循环使得其时间复杂度为O(n^2),在力扣上提交会超时。讲解中的贪心方法一实现上很简单,但思路确实不好想,要理解为什么“如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。”,相当于找一段数组的后缀,其剩余油量作为预先获得的油量,可以让车撑过累加的最小负数:

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        n = len(gas)
        # 代码随想录贪心一
        Sum, min_gas = 0, float('inf')
        for i in range(n):
            rest = gas[i] - cost[i]
            Sum += rest
            min_gas = min(min_gas, Sum)
        if Sum < 0:
            return -1
        if min_gas >= 0:
            return 0  # 从0出发过程中的最小剩余油量大于等于0,可以直接从0出发
        for i in range(n-1, -1, -1):
            min_gas += gas[i] - cost[i]
            if min_gas >= 0:
                return i
        
        return -1

LeetCode 135. 分发糖果

题目链接:135. 分发糖果 - 力扣(Leetcode)

 这道题在力扣上标记为困难题,题面要求的是相邻孩子评分更高(严格)要获得更多糖果,而求最少糖果数目,意味着相邻的评分更高的孩子糖果数只需要增加一个。排在中间的孩子涉及到两边评分的比较,因此贪心的思路需要分两次进行,先从左到右遍历,当前孩子评分比左侧高,让当前孩子的糖果数比左侧孩子的多一个;然后类似地从右到左遍历:

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        ans = [1] * n  # 每个孩子至少一个糖果,初始化为1
        # 从左到右遍历
        for i in range(1, n):
            if ratings[i] > ratings[i-1]:
                ans[i] = ans[i-1] + 1
        # 从右到左遍历
        for i in range(n-2, -1, -1):
            if ratings[i] > ratings[i+1]:
                if i > 0 and ratings[i] > ratings[i-1]:
                    ans[i] = max(ans[i+1], ans[i-1]) + 1
                else:
                    ans[i] = ans[i+1] + 1
        return sum(ans)

自己实现的时候忽略了从右到左遍历时,除了考虑当前孩子和右边孩子的评分,还要考虑如果当前孩子评分也比左边的高,则要取两边的最大值再加1,这样才能保证两边都满足。

你可能感兴趣的:(刷题,算法,贪心算法,leetcode)