贪心并不是一种算法,而是一种思想,所以不像二分那样一掌握固定的答题模式就会很快解决。贪心常常和其他知识点结合起来,所以题目有时并不好做。贪心在一次遍历中实现。
T1:买股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4] 输出:5 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1] 输出:0 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
我的理解是,这道题的贪心思想在于在遍历的每一次贪心地(每一步)取得最大利润和最小买入价格。在每一步选择当前最优的决策,这是贪心算法的核心思想。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit=0
min_prices=prices[0]
for i in prices[1:]:
profit=max(profit,i-min_prices)
min_prices=min(min_prices,i)
return profit
T2:跳跃游戏
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4] 输出:false 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
这道题的贪心思想我认为在于遍历中的每一步都贪心地取得在该处能跳跃的最大值,这样当无法再跳跃或者确定可以到达时便可很自然地终止。
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_reach=0
for i in range(len(nums)):
if max_reach>=len(nums)-1:
return True
max_reach=max(max_reach,i+nums[i])
if max_reach<=i:
return False
T3:跳跃游戏2
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向后跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是2
。 从下标为 0 跳到下标为 1 的位置,跳1
步,然后跳3
步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4] 输出: 2
class Solution:
def jump(self, nums: List[int]) -> int:
if len(nums)==1:
return 0
n=len(nums)
current_length=0
next_length=0
steps=0
for i in range(n-1):
next_length=max(next_length,i+nums[i])
if i==current_length:
steps+=1
current_length=next_length
if current_length>=n-1:
return steps
上一题问的是能不能到达,这一题则是最少的跳数。在遍历过程中,不断更新下一步能跳的最远位置。当遍历到当前能跳的最远位置时,进行一次跳跃,并更新当前位置为下一步能跳的最远位置。贪心思想在于:当遍历到当前能跳的最远位置时,进行一次跳跃,并更新当前位置为下一步能跳的最远位置。
T4:划分字母区间
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc"
能够被分为 ["abab", "cc"]
,但类似 ["aba", "bcc"]
或 ["ab", "ab", "cc"]
的划分是非法的。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij" 输出:[9,7,8] 解释: 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec" 输出:[10]
这道题就是展现贪心和哈希的结合了,先看一眼代码。
class Solution:
def partitionLabels(self, s: str) -> List[int]:
# 键是字符,值是索引,这样才会记录最后一个
last_one={char:idx for idx,char in enumerate(s)}
start=0
end=0
result=[]
for i,char in enumerate(s):
end=max(end,last_one[char])
if i==end:
result.append(end-start+1)
start=end+1
return result
这里把字符作为了唯一的键,因为字符会不断重复,而在字典中遇到重复的键时,值会替换为最后来的那一个,因此正好可以记录每个字母最后一次出现的索引。
本题贪心思想:在遍历过程中,动态更新当前分区的结束位置为字符的最后出现位置的较大值。当遍历到分区的结束位置时,记录当前分区的长度,并开始新的分区。
总结
在最后我进行一下总结,这四道题从题目上看起来风格不太相同,和昨天二分那些大同小异的题目不太一样,但是如果你能看出来题目可以使用贪心的思想去做,那就可以立即做一个for循环,然后在遍历过程中动态的更新最大或最小值即可,所以这样看来,其实贪心也是有模板的。