贪心算法理论基础
什么是贪心
贪心的本质是选择每一阶段的局部最优,从而达到全局最优
贪心的套路(什么时候用贪心)
- 说实话贪心算法并没有固定的套路。
- 最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。
- 刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心。
- 因为贪心有时候就是常识性的推导,所以会认为本应该就这么做!
贪心一般解题步骤
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
455. 分发饼干
贪心(排序后比较)
- 使得喂给每个孩子的是能够满足他胃口的最小的饼干
- 想清楚局部最优,想清楚全局最优,感觉局部最优是可以推出全局最优,并想不出反例,那么就试一试贪心。
class Solution:
# 思路1:优先考虑喂饼干
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
res = 0
for i in range(len(s)):
if res <len(g) and s[i] >= g[res]: #小饼干先喂饱小胃口
res += 1
return res
376. 摆动序列
贪心算法
- 局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
- 整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
- 即不断寻找下个符合条件的值,并记录
# Time complexity: O(n)
# Space complexity: O(1)
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
pre, cur, res = 0, 0, 1
for i in range(len(nums) - 1):
cur = nums[i + 1] - nums[i]
# cur and pre are in different signs
if cur * pre <= 0 and cur != 0:
res += 1
pre = cur
return res
动态规划
很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即nums[i] > nums[i-1]),要么是作为山谷(即nums[i] < nums[i - 1])。
- 设dp状态
dp[i][0],表示考虑前i个数,第i个数作为山峰的摆动子序列的最长长度 - 设dp状态
dp[i][1],表示考虑前i个数,第i个数作为山谷的摆动子序列的最长长度
则转移方程为:
dp[i][0] = max(dp[i][0], dp[j][1] + 1),其中0 < j < i且nums[j] < nums[i],表示将nums[i]接到前面某个山谷后面,作为山峰。dp[i][1] = max(dp[i][1], dp[j][0] + 1),其中0 < j < i且nums[j] > nums[i],表示将nums[i]接到前面某个山峰后面,作为山谷。
初始状态:
由于一个数可以接到前面的某个数后面,也可以以自身为子序列的起点,所以初始状态为:dp[0][0] = dp[0][1] = 1。
# Time complexity: O(n^2)
# Space complexity: O(n)
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
dp = []
for i in range(len(nums)):
dp.append([1, 1])
for j in range(i):
if nums[j] < nums[i]:
# nums[j] as the previous down, or do not take nums[j] into account
dp[i][0] = max(dp[j][1] + 1, dp[i][0])
if nums[j] > nums[i]:
# nums[j] as the previous up, or do not take nums[j] into account
dp[i][1] = max(dp[j][0] + 1, dp[i][1])
return max(dp[-1][0], dp[-1][1])
53. 最大子数组和
贪心
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。
# Time complexity: O(n)
# Space complexity: O(1)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
res = float('-inf')
cur = 0
for num in nums:
cur += num
if cur > res:
res = cur
# plus a negative number always make the result become smaller
if cur < 0:
cur = 0
return res
动态规划
# Time complexity: O(n)
# Space complexity: O(n)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0] = nums[0]
res = dp[0]
for i in range(1, len(nums)):
# either continue the subarray or start a new subarry with nums[i]
dp[i] = max(nums[i], dp[i-1] + nums[i])
res = max(res, dp[i])
return res