贪心算法理论基础
贪心的本质:通过每一阶段的局部最优推出全局最优。
思路:找局部最优,看能不能推出全局最优,并尝试举反例。
步骤:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优
455.分发饼干
首先想到的解法是先将s数组和g数组按照从小到大的顺序排序,然后用两个for循环来遍历两个数组,外层遍历g数组,内层遍历s数组,每次遇到满足g[i] < s[i]的情况时就将result+1,然后将s[i]置零代表这篇饼干已被使用过,并跳出当前层循环;若不满足则直接continue,查找下一片饼干。总体思路就是优先满足胃口小的孩子。
python
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
s.sort() # 排序,从小到大
g.sort() # 使小胃口的孩子和小饼干排列在前
result = 0 # 结果初始为0
for i in range(len(g)): # 遍历孩子数组
for j in range(len(s)): # 遍历饼干数组
if g[i] > s[j]: # 意味着饼干不能满足孩子胃口,直接跳过,寻找下一块饼干
continue
else:
s[j] = 0 # 满足条件则将当前饼干置零,防止后续再被使用
result += 1
break
return result
优化版本,不需要使用两个for循环,降低时间复杂度:
python
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
s.sort()
g.sort()
index = 0 # 用一个index来遍历胃口
for i in range(len(s)): # for循环遍历饼干
if index < len(g) and g[index] <= s[i]: # 仅当满足条件时,index才会+1
index += 1
return index # 返回index,也就是消耗的饼干数
这里必须外层遍历饼干,内层遍历胃口才可以,因为外层循环的i是固定移动的,内层的index只有在满足条件时才会移动,如果交换顺序就会导致一直在用最小的饼干来对比所有孩子的胃口,从而无法得出结果。
376.摆动序列
思路:局部最优------删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值 。整体最优------整个序列有最多的局部峰值,从而达到最长摆动序列。
实际上不需要进行删除操作,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)。如果前一对差值prediff与当前对差值curdiff的乘积小于0,那么就说明当前是一处摆动。代码如下:
python
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
if len(nums) <= 1: # 如果数组长度小于等于1,那么则直接返回数组的长度
return len(nums)
prediff, curdiff, result = 0, 0, 1 # 初始化前一对差值,当前差值和结果,因为输出的结果是序列的长度,所以结果的初值要设置为1(单纯计算正负差值的对数的结果与序列长度相差1)
for i in range(1, len(nums)): # 遍历数组,从1开始
curdiff = nums[i] - nums[i - 1] # 计算当前差值
if curdiff * prediff <= 0 and curdiff != 0: # 若当前差与前一对差乘起来的结果是负数或0,且当前差值不为0,那么说明出现了摆动
result += 1 # 结果+1
prediff = curdiff # 只有出现摆动时才令前一对差等于当前差
return result
53.最大子数组和
暴力解法,容易想到但会超时。
python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = float('-inf') # 结果初始为无穷小
for i in range(len(nums)): # i对应遍历的起始位置
sum = 0 # 每次改变起始位置时都将sum置零
for j in range(i, len(nums)): # 每次从j从i开始遍历寻找最大值
sum += nums[j]
if sum > result: # 如果sum大于之前的result,就进行赋值
result = sum
return result
贪心思路:
**局部最优------当前"连续和"为负数的时候立刻放弃,从下一个元素重新计算"连续和",因为负数加上下一个元素 "连续和"只会越来越小。**全局最优------选取最大"连续和"
遍历 nums,从头开始用sum累积,如果sum一旦加上nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积sum了,因为已经变为负数的sum,只会拖累总和。
这相当于是暴力解法中的不断调整最大子序和区间的起始位置。
python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = float('-inf') # result初始为无穷小
sum = 0 # 连续和初始为0
for i in range(len(nums)): # 遍历数组
sum += nums[i] # 将第i个元素加到连续和上
if sum > result: # 如果当前连续和大于result,则更新result的值
result = sum
if sum < 0: # 如果连续和小于0,则直接舍弃
sum = 0 # 将连续和置零
continue # 从下一个i开始重新计算,类似于暴力法的移动起始位置
return result