贪心算法 小明买钢笔 c语言,LeetCode 101:和你一起你轻松刷题(python版)第 2 章 最易懂的贪心算法...

LeetCode 101:和你一起你轻松刷题(python版)

注:作者:高畅 Chang Gao,原书为c++版本,解题思路清晰,知识点全面,是一本好书;翻译成python版本的解法可能未必是最优解法,由于本人是新手小白,算法实现是第一步,优化后面再弄。如有侵权,联系删除

第 2 章 最易懂的贪心算法

2.1 算法解释

顾名思义, 贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

举一个最简单的例子:小明和小王喜欢吃苹果,小明可以吃五个,小王可以吃三个。已知苹果园里有吃不完的苹果,求小明和小王一共最多吃多少个苹果。在这个例子中,我们可以选用的贪心策略为,每个人吃自己能吃的最多数量的苹果,这在每个人身上都是局部最优的。又因为全局结果是局部结果的简单求和,且局部结果互不相干,因此局部最优的策略也同样是全局最优的策略。

2.2 分配问题

455. Assign Cookies (Easy)

题目描述

有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。

输入输出样例

输入两个数组,分别代表孩子的饥饿度和饼干的大小。输出最多有多少孩子可以吃饱的数量

input: [1,2], [1,2,3]

output: 2

在这个样例中,我们可以给两个孩子喂 [1,2]、 [1,3]、 [2,3] 这三种组合的任意一种,两个孩子都可以吃饱

题解

因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可 以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这 个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。

简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。

至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。

"""

对两组数据排序,如果胃口最小的饼干满足最小胃口的孩子,则分配成功,不满足,则换大尺寸饼干

"""

class Solution:

def findContentChildren(self, g: List[int], s: List[int]) -> int:

g.sort()

s.sort()

num = min(len(g),len(s))

i, j = 0, 0

while i < len(g) and j < len(s):

if g[i] <= s[j]:

i += 1

j +=1

else:

j += 1

return i

135.Candy(Hard)

题目描述

一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比自己身旁的一个孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少个糖果。

输入输出样例

输入是一个数组,表示孩子的评分。输出是最少糖果的数量。

input: [1,0,2]

output: 5

在这个样例中,最少的糖果分法是 [2,1,2]。

题解

做完了题目 455,你会不会认为存在比较关系的贪心策略一定需要序或是选择?虽然这一道题也是运用贪心策略,但我们只需要简单的两次遍历即可:把所有孩子的糖果数初始化为 1;

先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1。

通过这两次遍历,分配的糖果就可以满足题目要求了。这里的贪心策略即为,在每次遍历中,只考虑并更新相邻一侧的大小关系。在样例中,我们初始化糖果分配为 [1,1,1],第一次遍历更新后的结果为[1,1,2],第二次遍历更新后的结果为 [2,1,2]。

"""

对于糖果的分发,孩子的位置已经固定,每个孩子得分也固定

从头遍历,遇到分数高的奖励前一名糖果数+1,

反向遍历,遇到分数高的奖励后一名的糖果数+1(应注意,遍历过程中若本身糖果数已经高于后一位则不需要奖励)

"""

class Solution:

def candy(self, ratings: List[int]) -> int:

nums = [1] * len(ratings)

for i in range(1, len(ratings)):

if ratings[i] > ratings[i - 1]:

nums[i] = nums[i - 1] + 1

for i in range(len(ratings) - 1, 0, -1):

if ratings[i] < ratings[i - 1]:

if nums[i - 1] <= nums[i]:

nums[i - 1] = nums[i] + 1

result = sum(nums)

return result

2.3 区间问题

435. Non-overlapping Intervals (Medium)

题目描述

给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。

输入输出样例

输入是一个数组,数组由多个长度固定为 2 的数组组成,表示区间的开始和结尾。输出一个整数,表示需要移除的区间数量。

input: [[1,2], [2,4], [1,3]]

output: 1

在这个样例中,我们可以移除区间 [1,3],使得剩余的区间 [[1,2], [2,4]] 互不重叠。

题解

在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。

具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。

在样例中,排序后的数组为 [[1,2], [1,3], [2,4]]。按照我们的贪心策略,首先初始化为区间[1,2];由于 [1,3] 与 [1,2] 相交,我们跳过该区间;由于 [2,4] 与 [1,2] 不相交,我们将其保留。因此最终保留的区间为 [[1,2], [2,4]]

注:排序可以根据实际情况判断区间按开头排序还是结尾排序,或开头与结尾单独排序

"""

如果判定重叠,需要移除一个,判定区间最小的,移除较大的一个才能够最大的实现无重叠区域

"""

class Solution:

def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:

intervals.sort()

j = 0

for i in range(len(intervals)-1):

if intervals[i+1][0] >= intervals[i][0] and intervals[i+1][0] < intervals[i][1]:

if intervals[i][1] <= intervals[i+1][1]:

intervals[i], intervals[i+1] = intervals[i+1],intervals[i]

j += 1

return j

2.4 练习

基础难度

605. Can Place Flowers (Easy)

采取什么样的贪心策略,可以种植最多的花朵呢?

452 Minimum Number of Arrows to Burst Balloons (Medium)

这道题和题目 435 十分类似,但是稍有不同,具体是哪里不同呢?

763 Partition Labels (Medium)

为了满足你的贪心策略,是否需要一些预处理?

注意 在处理数组前,统计一遍信息(如频率、个数、第一次出现位置、最后一次出现位置等)可以使题目难度大幅降低。

122 Best Time to Buy and Sell Stock II (Easy)

股票交易题型里比较简单的题目,在不限制交易次数的情况下,怎样可以获得最大利润呢?

进阶难度

406 Queue Reconstruction by Height (Medium)

温馨提示,这道题可能同时需要排序和插入操作。

"""

605 种花问题

本题涉及到最优种花问题,使用贪心算法,每(0,0,0)可以种植一棵(边界除外)

解题技巧,在边界前和后再填充一个数,这样可以解决所有边界出现的特殊情况(如只有一个值等)

"""

class Solution:

def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:

flowerbed = [0] + flowerbed + [0]

for i in range(1, len(flowerbed)-1):

if [flowerbed[i-1],flowerbed[i],flowerbed[i+1]] == [0,0,0]:

n -= 1

flowerbed[i] = 1

if n > 0:

return False

else:

return True

"""

452 用最少数量的箭引爆气球

本题涉及到最优射击问题,用最少的钉射中最多的气球,就是找到最大的相交区间

解题技巧:根据数列尾部排序,只要前区间大于最前面的后区间就判定+1,否则必定有相同区间

"""

class Solution:

def findMinArrowShots(self, points: List[List[int]]) -> int:

if not points:

return 0

points.sort(key=lambda x:x[1])

j = 1

first_end = points[0][1]

for i in range(1, len(points)):

if points[i][0] > first_end:

j += 1

first_end = points[i][1]

return j

"""

763 划分字母区间

本题涉及到最大划分区域,找到每个字母形成的区域,并确定最大并集

使用循环找到每个字母形成的最大区间

解题技巧:找到字母出现的最后位置(使用了enumerate()函数)

使用for循环找到每个字母形成的最大范围(head与tail)

"""

class Solution(object):

def partitionLabels(self, S):

last = {value: key for key, value in enumerate(S)}#每一个字符串出现的最后位置

#print(last)#{'a': 8, 'b': 5, 'c': 7, 'd': 14}

head = tail = 0

result = [] # 结果列表

for key, value in enumerate(S):

tail = max(tail, last[value])

if key == tail:

result.append(tail - head + 1)

head = tail + 1

return result

"""

122 买卖股票的最佳时机 II

解题思路:由于只能买和卖只能交替进行,故最低买最高卖并不能得到最优解

解题技巧:题目并没有规定同一天只可以交易一次,因此在交易过程中,贪心算法只要后一天高于当天,即可当天买入第二天卖出,同理可以第二天再买入

"""

class Solution:

def maxProfit(self, prices: List[int]) -> int:

profit = 0

for i in range(1, len(prices)):

if prices[i] > prices[i-1]:

profit += prices[i] - prices[i-1]

return profit

"""

406 根据身高重建队列

本题为排序题,需要找到自己的位置,位置由身高h 和 前面身高不低于自身的k值确定

解题思路:K值大于0移动相应位置,首先针对身高进行排序,使用people.sort(key=lambda x:(-x[0],x[1]))方法将h和K相反的排序方式,身高采用倒序更容易实现(遍历原列表,在新列表中根据K值排序)

"""

class Solution:

def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:

if not people:

return []

people.sort(key=lambda x:(-x[0],x[1]))

new_people = []

for p in people:#將所有人插入正确的位置

new_people.insert(p[1],p)

return new_people

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值