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_sum
和total_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