Leetcode整理一|01贪心算法
首先要感谢《谷歌高畅Leetcode刷题笔记》的作者,本系列文章的发表是基于他的C++版本提出的python版本,并且添加一些自己的总结。
贪心算法☺
贪心算法的思想就是,每一步最优就可以达到全局最优。
贪心,顾名思义,就是每一步都很贪心,也就是每一步都按照它认为可以达到最优的方法进行。
练习题一 455分配问题
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
链接:https://leetcode-cn.com/problems/assign-cookies
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
这道题目其实并不难想,就是对每个饼干,把它送给能吃下它的胃口最大的人,因为如果胃口小(s[j] >g[i])就有点浪费,所以尽可能要给胃口大的。
碰到分配问题的时候提前排序是一个比较好的想法,这里g和s的排序方式应该是一样的,都是从小到大;
对于每块饼干:
分到了:因为它>=某个g
没分到:它<小于所有g
所以代码是这样的:
class Solution(object): def findContentChildren(self, g, s): """ :type g: List[int] :type s: List[int] :rtype: int """ g = sorted(g) s = sorted(s)#或者直接s.sort(),这个时候不能重新赋值 num = 0 for bingan in s: if len(g): if bingan < g[0]: continue else: num+=1 g.pop(0) return num
关于sorted和sort函数:https://www.cnblogs.com/hedianzhan/p/9628435.html
这样出来的结果没有一个时间/空间是超过95%以上的。
整理了一下:
函数 | sorted() 默认从小到大升序 |
使用范围 | 可迭代对象都可以用 |
使用方法 | sorted (itrearble, cmp = None , key = None , reverse = False )#itrearble可以是数组,字符串 |
特殊使用 |
# #对键值对组成的元组的列表 key = lambda x:x[1] #关键词key传入,对元素取值 |
因为整理所花费的时间太过可观,接下来尽量只放链接,然后优化思路这样子。作为资源合集
一个大神整理的解法大全,包含很多通过99%的算法:
https://leetcode-cn.com/problems/assign-cookies/solution/dan-shuang-pai-xu-you-xian-dui-lie-9jie-uhwdm/
我写的内存能击败98.7%的:https://leetcode-cn.com/problems/assign-cookies/solution/shuang-zhi-zhen-pythonnei-cun-ji-bai-987-2jk9/
练习题二 135Candy问题
这个分发糖果问题,大的孩子一定要得到更多的糖果,每个孩子都至少有一个糖果。
从左往右遍历一次,右边的大于左边的都可以拿到更多糖果;
从右边往左遍历一次,左边大于右边的可以拿到更多糖果。
代码如下:
class Solution(object): def candy(self, ratings): """ :type ratings: List[int] :rtype: int """ #从左往右发一遍糖果,再从右往左发一遍糖果。 candy_list = [1]*len(ratings) for i in range(1,len(ratings)): if ratings[i]>ratings[i-1]: candy_list[i]=candy_list[i-1]+1 for i in range(len(ratings)-1,0,-1): if ratings[i-1]>ratings[i]: if candy_list[i-1]<=candy_list[i]: candy_list[i-1] = candy_list[i]+1 return sum(candy_list)
这个算法的时间复杂度超过了94.77%的用户,空间71.07%。
内存有没有可能更优呢?答案给出了常数空间遍历的代码。两个指标都能击败98 95%的用户,真是牛逼!
但是答案的分析就是基于例子的分析的。我们基于之前实现的两次遍历贪心试试:
从左往右的时候,如果右边更高——>+1
如果右边没有更高—>reset 1——>右边更高——>+1
右边没有更高:这个和右边都要加1
class Solution(object): def candy(self, ratings): """ :type ratings: List[int] :rtype: int """ n = len(ratings)#记录ratings长度 ret = 1#记录糖果数量 inc, dec, pre = 1, 0, 1#递增长度,递减长度,当前糖果数量 for i in range(1, n): if ratings[i] >= ratings[i - 1]: dec = 0 pre = (1 if ratings[i] == ratings[i - 1] else pre + 1) ret += pre inc = pre else: dec += 1 if dec == inc:#等长的时候,到顶了,把顶点包进来; dec += 1 ret += dec pre = 1 return ret
练习题三 435 区间问题
移除最少的区间,让剩下的区间不重叠;
就是要让保留下来的区间数量尽可能大;
保留的要尽可能大,那么,对于同一个start,end要尽可能的小
先对数组区间进行排序,如果当前的star一样大:扔掉;如果不一样大,但是新来的尾巴更短:扔掉当前的,换成它;如果不一样大,尾巴也没更短,并且start还没人家的end长:扔掉;剩下就是死里逃生出来的,可以换成它。
class Solution(object): def eraseOverlapIntervals(self, intervals): """ :type intervals: List[List[int]] :rtype: int """ if not len(intervals): return 0 intervals = sorted(intervals) depict_num = 0 now = intervals[0] for i in range(1,len(intervals)): if (intervals[i][0] == now[0]): depict_num+=1 elif intervals[i][1] < now[1]: # 取尾巴最短的 now = intervals[i] depict_num+=1 elif intervals[i][0] < now[1]: depict_num+=1 else: now = intervals[i] return depict_num
课后习题
605.种花问题:第一朵要立刻决定中不中,然后判断i-1,i和i+1是不是都是0.最后一朵要判断i-1;足够贪心,遍历一遍就行。
class Solution(object): def canPlaceFlowers(self, flowerbed, n): """ :type flowerbed: List[int] :type n: int :rtype: bool """ if n == 0: return True if len(flowerbed)==1: if flowerbed[0]==0 and n<=1: return True else: return False nums = 0 if flowerbed[0]==0 and flowerbed[1]==0: flowerbed[0]=1 nums+=1 for i in range(1,len(flowerbed)-1): if flowerbed[i-1]==0 and flowerbed[i+1]==0 and flowerbed[i]==0: flowerbed[i]=1 nums+=1 if flowerbed[len(flowerbed)-1]==0 and flowerbed[len(flowerbed)-2]==0: flowerbed[len(flowerbed)-1]=1 nums+=1 if nums >= n: return True else: return False
452.引爆气球:看问题一定要分析本质。用最少的箭引爆气球,也就是说是找最小的不重叠区间:
从左往右来说,区间不相交,箭头直接加1;如果相交,移动上一个箭头,移动到相交的这个区间上面来,对于下一个气球,如果最左边在这个区间上,那很省事,否则就重新加一个箭头。
class Solution(object): def findMinArrowShots(self, points): """ :type points: List[List[int]] :rtype: int """ points.sort()#points这里表示成箭头的区间了 i = 1 while i < len(points): (al, ar), (bl, br) = points[i - 1], points[i] if bl <= ar: points[i - 1] = bl, min(ar, br) points.pop(i) else: i += 1 return len(points)
763.划分字母区间:
class Solution(object): def partitionLabels(self, S): """ :type S: str :rtype: List[int] """ #分析:如果a出现在第一个,那么数组的最后一个a之前的所有字母都必须包含在片段里 #如果片段里包含其他的字母,其他字母的扩展也同样的 #直到所有的start到end的都被包含才能进行下一步 List = [] while len(S): pianduan_dict = S[0] S=S[1:] number = 1 while len(pianduan_dict) and len(S): if pianduan_dict[0] in S: pianduan_dict+=S[0] S=S[1:] number+=1 else: pianduan_dict=pianduan_dict[1:] List.append(number) if len(S)==1: List.append(1) S = "" return List
122.股票交易:处理成当天买入卖出即可
class Solution(object): def maxProfit(self, prices): """ :type prices: List[int] :rtype: int """ #判断第i-1是不是小于第i个,只有小于的时候,才把i-1屯下来 #判断第i是不是小于第i+1,如果是,就要卖出 earn = 0 for i in range(1,len(prices)): if prices[i-1]>prices[i]: pass elif prices[i-1]<=prices[i]: earn = earn+prices[i]-prices[i-1] return earn
406.根据身高重建队列:我按官方的思路写了代码。
class Solution(object): def reconstructQueue(self, people): """ :type people: List[List[int]] :rtype: List[List[int]] """ people.sort(key=lambda x: (x[0], -x[1])) people = people[::-1] # 得到[[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]] # 每次从左往右进行判断即可,因为右边的元素插入左边不会影响左边的数值 ans = [] def get_pos(List,person): # 得到person应该被放在第几个位置 height = person[0] number = person[1] now_number = 0 for i in range(len(List)): if List[i][0]>=height: now_number+=1 if now_number==number: return i+1 return 0 for i in people: if not len(ans): ans.append(i) else: pos = get_pos(ans,i) ans.insert(pos,i) return ans
看看官方的代码吧:
class Solution(object): def reconstructQueue(self, people): """ :type people: List[List[int]] :rtype: List[List[int]] # 身高从小往大的排列,人数从多往少 """ people.sort(key=lambda x: (-x[0], x[1])) n = len(people) ans = list() for person in people: ans[person[1]:person[1]] = [person] return ans
大概这就是我想退学的原因。ans[person[1]:person[1]]插入元素
其实get_pos得到的就是i[1],因为前面大于等于它的数量给定了呀。修改后的代码为:
class Solution(object): def reconstructQueue(self, people): """ :type people: List[List[int]] :rtype: List[List[int]] """ people.sort(key=lambda x: (x[0], -x[1])) people = people[::-1] ans = [] for i in people: if not len(ans): ans.append(i) else: #pos = get_pos(ans,i) ans.insert(i[1],i) return ans
665.非递减数列:暴力破解,每次都扔一个数,直到判断为True;如果找不到,返回False
class Solution(object): def checkPossibility(self, nums): """ :type nums: List[int] :rtype: bool """ def get_flag(List): flag = True for i in range(1,len(List)): if List[i]>=List[i-1]: continue else: flag=False return flag return flag nums2 = nums[:] flag = False for i in range(len(nums)): nums2.pop(i) if get_flag(nums2): return True else: nums2 = nums[:] return flag
其实这题就是在判断到底是nums[i]出问题了,还是nums[i-1]出问题了。并且用替换继续遍历会比pop删除更好。
class Solution(object): def checkPossibility(self, nums): """ :type nums: List[int] :rtype: bool """ N = len(nums) count = 0 for i in range(1, N): if nums[i] < nums[i - 1]: count += 1 if i == 1 or nums[i] >= nums[i - 2]: nums[i - 1] = nums[i] else: nums[i] = nums[i - 1] return count <= 1