LeetCode 122.买卖股票的最佳时机Ⅱ:
思路:
贪心:
不断尝试买进的时间。因为某一次买入卖出所得的利润可以分解为其中一天买进次日卖出,并不断进行累积的利润。
假设当前买进了,如果下一天价格比当前高,那么就真的买进卖出;
如果下一天价格比当前低,那么就当作当前没有买,那么就是假的买进卖出。
也就是只累积次日价格 > 当日价格,即正利润
class Solution:
def maxProfit(self, prices: List[int]) -> int:
lenPrices = len(prices)
if lenPrices == 1:
return 0
pre, cur = prices[0], 0 # pre为假设买入的价格,cur为卖出的价格
profit = 0 # 利润
for i in range(1, lenPrices):
cur = prices[i]
if cur > pre: # 只有当cur > pre,才真正买进卖出
profit += cur - pre
pre = cur # 不断买进卖出
return profit
"""
换一种写法
"""
class Solution:
def maxProfit(self, prices: List[int]) -> int:
lenPrices = len(prices)
if lenPrices == 1:
return 0
profit = 0
for i in range(1, lenPrices):
profit += max(prices[i] - prices[i-1], 0)
return profit
感悟:
逐天累积利润,且只累积正利润
LeetCode 55.跳跃游戏:
思路:
使用跳跃能到的最大覆盖范围,如果范围包括了最后一个下标,那么能够达到最后一个下标,否则不能达到。
最大覆盖范围cover = max(i+nums[i], cover)
逐个遍历数组中的元素,i 的范围为[0, cover],每次更新cover,如果cover >= len(nums) - 1,说明能够到达最后一个下标;如果 i 遍历完成,说明cover不能达到最后一个下标
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
i = 0
while i <= cover:
cover = max(i + nums[i], cover)
if cover >= len(nums) - 1: # 最大覆盖范围覆盖了最后一个下标
return True
i += 1
return False
感悟:
转换跳跃距离为最大覆盖范围,从而能否跳到最后一个下标从怎么跳跃转换为了最大覆盖范围能否包括最后一个下标。
同时注意遍历数组的 i 范围为[0, cover],且为逐个遍历
LeetCode 45.跳跃游戏Ⅱ:
思路:
还是使用最大覆盖范围,不过因为要统计步数,因此要区分当前步数的最大覆盖范围和下一步的最大覆盖范围。
1)要想要跳的步数最少,那么在当前覆盖范围固定且不能到达数组最后一个位置时,需要下一步的覆盖范围最大。因此遍历当前覆盖时,使用max求下一步最大覆盖范围。
2)什么时候更新curcover:如果当前覆盖范围没能到达数组最后一个位置(可以设置成遍历时的当前覆盖范围是到不了最后一个位置的,通过初始化和排除只有一个元素的数组达到),且 i 已经遍历到了当前覆盖范围的最后一个位置,也就是下一步最大覆盖范围求出来了,那么就需要更新当前覆盖范围,同时增加步数,也就是跳一步。
3)什么时候跳出循环:更新当前覆盖范围之后,判断更新后的当前覆盖范围是否包含数组最后一个元素(因为 i 遍历时的当前覆盖范围不包含数组最后一个元素)。如果包含,跳出循环;否则进行下一轮求下一步最大覆盖范围。
PS:由上图可知,curcover更新的次数,也就是最少步数
class Solution:
def jump(self, nums: List[int]) -> int:
lenNums = len(nums)
if lenNums == 1: # 排除了只有一个元素的数组
return 0
# curcover是当前覆盖范围,nextcover是下一次跳跃能覆盖的范围
# curcover从0开始,也就是最开始的curcover只有nums[0]
curcover, nextcover = 0, 0
jumpStep = 0 # 跳跃步数,也就是cover更新的次数
i = 0
# 遍历curcover时,curcover不包含数组最后一个位置
for i in range(lenNums):
nextcover = max(nextcover, i + nums[i])
if i == curcover: # 更新覆盖范围
jumpStep += 1 # 步数 + 1
curcover = nextcover
# 更新后的范围是否包括最后一个位置
if curcover >= lenNums - 1:
break
return jump
感悟:
当前最大覆盖范围和下一步最大覆盖范围,以及更新当前最大覆盖范围的次数也就是最少跳跃步数,在 i 遍历到当前最大覆盖范围最后一个元素时,更新curcover;i 遍历时的curcover默认不包含数组最后一个元素(因为curcover最开始只有nums[0],为了保证curcover不包含数组最后一个元素,因此将只有一个元素的数组单独判断)
LeetCode 1005.K次取反后最大化的数组和:
思路:
贪心的思路为:
第一次局部最优:首先对nums中的负数中的最小值进行取反,得到新的负数集合,再对新的负数集合中的最小值进行取反
如果还有取反次数,此时nums为非负整数数组
第二层局部最优:不断对nums的最小值进行取反
"""
方法1:直接按照思路来
首先对数组进行排序,得到顺序数组
然后不断对数组的前面负数进行取反
如果还有取反次数,对新的nums数组中的最小值进行取反
但是需要注意的细节很多
1. 负数取反时,while条件为 i <= len(nums) - 1 and nums[i] < 0 and k > 0,
因为会出现nums全为负数且k > len(nums)的情况,所以要加 i <= len(nums) - 1
2. 如果还有取反次数,对新nums数组中的最小值进行取反,此时的最小值在数组中间
2.1 如果开始nums有正数也有负数,那么最小值为min(nums[i - 1], nums[i]),但是因为是对数组元素进行取反,所以要分开判断取反(记得加 i > 0条件)
2.2 如果nums只有负数,那么此时 k > len(nums),while循环后i = len(nums),此时最小值为nums[i - 1]
2.2 如果nums只有正数,由于i初始化为0,因此最小值为nums[i]
"""
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
nums.sort()
i = 0
lenNums = len(nums)
while i <= lenNums - 1 and nums[i] < 0 and k > 0: # 取反负数
nums[i] = -nums[i]
i += 1
k -= 1
if k > 0: # 还有剩余的取反次数
# 如果nums为全负数,那么while之后i = lenNums,最小值为nums[i - 1]
# 如果nums部分负数部分正数,如果最小值为nums[i - 1],那么条件为i > 0 and nums[i - 1] < nums[i]
if i == lenNums or (i > 0 and nums[i - 1] < nums[i]):
nums[i - 1] = nums[i - 1] if k % 2 == 0 else -nums[i - 1]
else:
nums[i] = nums[i] if k % 2 == 0 else -nums[i]
# 求数组最大和
sumNums = 0
for i in range(len(nums)):
sumNums += nums[i]
return sumNums
"""
方法2:方法1的优化,或者说没有方法1直接
1. **对数组按照绝对值逆序排序**
2. 对负数求反,遍历数组,如果k > 0且nums[i]为负数,求反
3. 如果还有取反次数,对最小值不断取反,即对nums[-1]不断取反
(此时最小值只为nums[-1],与方法1的区别)
4. 求k次取反后数组的和可以在取反过程中得到,对负数取反遍历数组时记录数组和sumNums,
如果还有取反次数且取反次数为奇数,那么sumNums += nums[-1] * 2,其中nums[-1]是取反后的
"""
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
nums.sort(key=lambda x: abs(x), reverse=True) # 按绝对值反向排序
sumNums = 0
for i in range(len(nums)):
if nums[i] < 0 and k > 0: # 遇到负数取反
nums[i] *= -1
k -= 1
sumNums += nums[i] # 记录数组和
if k % 2 == 1: # 如果还有取反次数
nums[-1] *= -1
sumNums += nums[-1] * 2
return sumNums
感悟:
- 需要清楚贪心的两次局部最优,以及方法1中的细节,对负数求反时注意不要超界,剩下取反次数对最小值取反时,什么时候最小值为num[i - 1],即开始nums全为负数,或 i > 0 and nums[i - 1] < nums[i]
- 方法2求k次取反后的数组和,在还有取反次数对正数取反时,如果剩下的取反次数为奇数,那么sumNums -= 2*abs( nums[-1]),(减去的两个:一个是前面对负数取反时加的abs(nums[-1]),一个是对nums[-1]取反后要减去的nums[-1])
学习收获:
买卖股票的最佳时机,以两天为周期,第一天买入,第二天卖出,只记录正利润
跳跃游戏:最大覆盖范围,如果要求最小步数,那么最大覆盖范围要拆分为当前curcover和下一步nextcover,且curcover不能到达数组最后一个位置
K次取反:方法1的细节,方法2对数组按照绝对值逆序排序可以去掉方法1中需要注意的细节