贪心算是一种常识性的思路,没有固定规律套路可以总结,有时会和动态规划混淆,反正记住一点,就是能从子问题的局部最优推导出全局最优解。
LeetCode 455 分发饼干
自己做题时想到用小尺寸的饼干优先满足小胃口的孩子,在代码实现时先遍历饼干,再遍历孩子,但写着写着思路有些混乱和不确定,一直想会不会有反例,干脆去看题解梳理一下。实际思路和遍历顺序没有问题,就是不自信:
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
# 遍历饼干
j = 0
res = 0
for i in range(len(s)):
# 胃口最小的孩子优先给饼干
if j < len(g) and g[j] <= s[i]:
res += 1
j += 1
return res
当时思路混乱主要是因为for循环内部想用while遍历g,思维就陷入了怪圈。
LeetCode 376 摆动序列
这题乍一看没什么思路,只能看讲解了,然后发现我一开始的找局部最大最小值的思路是对的,但数数数错了,以为方案不可行,而代码实现还得考虑边界条件,以及平坡的问题。看力扣评论区同学的思路很简洁,这里借鉴一下:
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
# 借鉴评论区PlanB同学的思路,平坡的相等元素直接跳过,通过flag记录上一次是上坡还是下坡
Sum, flag = 1, 0 # 结果初始化为1,考虑了两端
for i in range(1, len(nums)):
if nums[i] > nums[i-1] and (flag == 0 or flag == -1):
Sum += 1
flag = 1 # 当前差值为正数
elif nums[i] < nums[i-1] and (flag == 0 or flag == 1):
Sum += 1
flag = -1
return Sum
代码随想录中还介绍了动态规划和线段树优化的思路,之后有时间可以再回顾。
LeetCode 53 最大子序和
题目链接:
虽然是简单题,但看到一刷时我写的是动态规划的解法,感觉事情不简单:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
res = nums[0]
# dp数组表示以位置i结尾的子数组的最大和
dp = [0 for _ in range(n)]
dp[0] = nums[0] # 初始化
for i in range(1, n):
# 两种情况:在dp[i-1]的基础上考虑当前的元素,加上nums[i];
# 第二种情况是dp[i-1]与nums[i]和为负数,则子数组从当前位置重新开始
dp[i] = max(dp[i-1] + nums[i], nums[i])
# 选取dp数组中的最大值
for i in range(n):
res = max(res, dp[i])
return res
印象中这是最初让我比较困扰的动态规划类型题,稀里糊涂地过了(痛苦面具),但感觉讲解中的贪心思路和上面的代码实现很相似,“局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。” 并且数组中的元素包含负数,贪心比较的时候不能和0比,而是和数据范围之外的负数比:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
# 贪心
res = float('-inf')
s = 0
for i in range(n):
s += nums[i]
# 及时收集当前最大子数组和的结果,相当于动态调整子数组右边界
if s > res:
res = s
# 贪心
if s <= 0:
s = 0
return res