Bootstrap

代码随想录算法训练营第三十五天| 122.买卖股票的最佳时机 II、55. 跳跃游戏、45.跳跃游戏 II

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

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

 

贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

局部最优可以推出全局最优。

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

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

这么一道简单题,就用了两次贪心!

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K--
  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
  • 第四步:求和
class Solution:
    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        #按照绝对值的大小进行排序
        nums.sort(key=lambda x: abs(x), reverse=True)  # 第一步:按照绝对值降序排序数组A
        print(nums)
        for i in range(0,len(nums)):
            if nums[i] < 0 and k > 0 :
                nums[i] *= -1
                k = k - 1
        #第二步贪心,此时已经达到了数组最大值的极限,最后的和不可能比这个状态还大,只能让其减少的最少,也就是对最后一个数进行变化,用while循环性能比较差,也可以直接判断k的奇偶
        #①
        while k >0:
            nums[-1] = nums[-1] *-1
            k = k -1 
        #②
        # if k >0:
        #     nums[-1] = nums[-1]*((-1)**(k % 2)) #这里-1要加上括号,因为运算符的优先级会导致最终错误的
        #     print(nums[-1]) 
        ##③
        # if k % 2 == 1:  #
        #     nums[-1] *= -1
        sum = 0
        for i in range(0,len(nums)):
            sum += nums[i]
        return sum

134. 加油站

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的

  • 每个加油站的剩余量rest[i]为gas[i] - cost[i]。

  • i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        cur_sum = 0  # 当前起点开始的油量余额
        total_sum = 0  # 整个行程的总油量余额
        start = 0  # 起始加油站的索引

        for i in range(0, len(gas)):  # 遍历每个加油站
            cur_sum += (gas[i] - cost[i])  # 更新当前油量余额,计算获得的油量减去到下一个站的花费
            total_sum += (gas[i] - cost[i])  # 更新总油量余额,同样计算获得的油量减去花费
            
            if cur_sum < 0:  # 如果当前油量余额为负,表示不能从当前起点出发
                cur_sum = 0  # 重置当前油量余额
                start = i + 1  # 将起点移动到下一个加油站
        
        if total_sum < 0:  # 如果总油量余额为负,表示无法完成整个环路
            return -1  # 返回-1表示无法完成环路
        
        return start  # 如果总油量余额不为负,返回起始加油站的索引

这里有个问题就是start变成i+1,但是并没有跑完一圈,如果长度为4,更新后的起始是2,那么只是加了2,3,并没有重新加0,1,是如何确定的后面两个加起来大于等于0就能得出i+1是起点呢

  • cur_sum小于0时,我们知道无法从当前start出发,因为我们无法到达当前位置i。因此,我们将start更新为i + 1,并重置cur_sum
  • 继续遍历剩余的加油站,更新cur_sumtotal_sum
  • 遍历结束后,如果total_sum为非负,则表示从某个加油站出发可以完成整个环路。
  • 因为在我们重新设定start之后,所有先前的起点都已经被验证无法完成环路,所以最后的start一定是有效的起点。

个人还是没有搞懂,之后再看,可能这就是贪心的一种表现,只要cur_sum大于0,就不会重新开始

135. 分发糖果

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

两个要求:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

最后是要找满足要求的最少的糖果数量

假设得分:[1,2,2] 那么后面有两个相邻得分一样的,我们是给左边的孩子更多糖果,还是给右边的孩子更多糖果呢?每个孩子是至少一个,最后的就是[1,2,1],

这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼(这个还没有明白具体是什么逻辑)

如果在考虑局部的时候想两边兼顾,就会顾此失彼。如果左右都要比较的情况下一定要分两次比较,不然会混乱

那么本题采用了两次贪心的策略:

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

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

class Solution:
    def candy(self, ratings: List[int]) -> int:
        candidate = [1] *len(ratings)
        #比较右边比左边大的情况
        for i in range(1,len(ratings)):
            if ratings[i] > ratings[i-1]:
                candidate[i] = candidate[i-1] + 1
        #比较左边比右边大的情况,需要倒叙遍历
        for i in range(len(ratings)-2,-1,-1):#注意这里后面要写上步长
            if ratings[i] > ratings[i+1]:
                # candidate[i] = candidate[i+1] + 1#
                #取决于candidate[i+1] + 1是否一定比candidate[i]大
                candidate[i] = max(candidate[i+1] + 1,candidate[i])
        sum = 0
        for i in candidate:
            sum += i
        return sum

最后为什么要取max。可以手动推导一下

ratings =[1,3,4,5,2]的情况,就会发现candidate[i+1] + 1不一定比candidate[i]大,所以要加max

;