455.分发饼干(贪心问题)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
一个小朋友最多只能拥有一块饼干。
example:
输入: [1,2], [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
链接:https://leetcode-cn.com/problems/assign-cookies
解题思路
- 贪心算法,每次用最小物资满足最小的需求。
步骤:
-
- 重排物资与需求
-
- 从小到大比对物资&需求
-
- 需求满足,两者++一位,需求不满足,物资++一位
代码
class Solution(object):
def findContentChildren(self, g, s):
g.sort()
s.sort()
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
435. 无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
链接:https://leetcode-cn.com/problems/non-overlapping-intervals/
example:
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
解题思路
- 贪心算法,每次保证最小量的局部最优解,结果就是全局最优解
-
- 将所有区间按右边界排序
-
- curr区间作为指针,cur_end从负无穷开始(float("-inf"))
-
- 只要cur_end<=区间左边界,该区间保留,且替换cur_end为该区间右边界
代码
class Solution(object):
def eraseOverlapIntervals(self, intervals):
intervals.sort(key=lambda x:x[1])
cur_end = float("-inf")
num = 0
for start,end in intervals:
if cur_end <= start:
cur_end = end
num += 1
else:
pass
return len(intervals) - num
452. 用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:
2
解题思路
- 贪心算法,以区间结尾排序
- 如果cur_end 小于 下一个的start,那箭数+1。
- 注意:要讨论points为0的情况
代码
class Solution(object):
def findMinArrowShots(self, points):
"""
:type points: List[List[int]]
:rtype: int
"""
if not points:
return 0
points.sort(key=lambda x:x[1])
cur_end = points[0][1]
arrows = 1
for begin,end in points:
if cur_end < begin:
arrows += 1
cur_end = end
else:
pass
return arrows
406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
解题思路
- 因为低身高的插入对高身高类间顺序无任何影响,所以
- 1.先按照身高降序,序号升序排列。
- 2.把同一身高的,按照序号插入对应队列序号中即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNniDkIf-1585574723920)(https://pic.leetcode-cn.com/f29bc8e24584fa239576933b62b20b61ad3748f91a02a590283c263f1d7ccf62-image.png)]
代码
class Solution(object):
def reconstructQueue(self, people):
people.sort(key=lambda x:(-x[0],x[1]))
output = []
for p in people:
output.insert(p[1],p)
return output
121.买卖股票的最佳时机(一次遍历法&双指针法)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
- 示例:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
一次遍历法:
- 遍历每天价格
- 用minprices记录当天是否为之前的最低价,然后用maxprofit来记录之后的价格差是否为最大利润。
- p.s 这里有一个时间线的概念,很多人说如果最后一天是最低价怎么办其实是不对的,因为买入必须必卖出早。minpices记录的是相对之前的最低价即可。
- 时间复杂度O(n):一共就遍历一次,跟列表长度n有关
- 空间复杂度O(1):常数个变量
代码
class Solution(object):
def maxProfit(self, prices):
minprices = int(1e9) #1e9 = 10^9
maxprofit = 0
for price in prices:
maxprofit = max(price - minprices,maxprofit)
minprices = min(price,minprices)
return maxprofit
暴利循环法
- 双指针吧,超时了。i从左,j从右。
- 每一个i值时,j遍历整个列表找最大值
- 时间复杂度O(n^2):i需要循环n-1次,j需要循环n-i-1次,相数是1,所以总共循环:(n-1+1)*(n-1)/2
- 空间复杂度O(1):只用了常数个变量
class Solution:
def maxProfit(self, prices: List[int]) -> int:
ans = 0
for i in range(len(prices)):
for j in range(i + 1, len(prices)):
ans = max(ans, prices[j] - prices[i])
return ans
122. 买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii
- 示例
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
解题思路
- 因为可以同时买出买入(当天卖了当天又买),要获得最大收益,只用保证每笔都赚钱就行
- 即prices[i-1]<prices[i]
- e.g 把题目当成简单的数学问题去计算最大值就好,不懂就画图。不要被题干代入成文字理解题。
代码
class Solution(object):
def maxProfit(self, prices):
maxprofit = 0
for i in range(1,len(prices)):
if prices[i-1] < prices[i]:
maxprofit += prices[i] - prices[i-1]
return maxprofit
605. 种花问题
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。
链接:https://leetcode-cn.com/problems/can-place-flowers
输入: flowerbed = [1,0,0,0,1], n = 1
输出: True
解题思路
- 题干解读后就一个条件:三连0位,输出+1
- 首尾补零解决边界问题,用于判断i=0&i=len(flowerbed)位是否可以种花
代码
class Solution(object):
def canPlaceFlowers(self, flowerbed, n):
newflowerbed = [0] + flowerbed + [0]
num = 0
i = 1
while i <= len(newflowerbed)-2:
if newflowerbed[i-1]==0 and newflowerbed[i]==0 and newflowerbed[i+1]==0:
i += 2
num += 1
else:
i += 1
return num >= n
392. 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
例如,"ace"是"abcde"的一个子序列,而"aec"不是
链接:https://leetcode-cn.com/problems/is-subsequence
解题思路
- 顺序固定,判断s[i]==t[j]即可
- 时间复杂度O(n^2)
- 空间复杂度O(1)
代码
class Solution(object):
def isSubsequence(self, s, t):
i,j = 0,0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
else:
j += 1
return i==len(s)
665. 非递减数列
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),总满足 array[i] <= array[i + 1]。
链接:https://leetcode-cn.com/problems/non-decreasing-array
解题思路
- 分析题目:当出现nums[i-1]>nums[i]时,改变nums[i-1] or nums[i],可以使数列只通过一次变成非递减。
- 方案:因为从前往后遍历,使nums[i-1] = nums[i]可以不干扰后面的结果。
- 一种例外情况:如果nums[i-1]>nums[i-2]>nums[i],只能使nums[i]=nums[i-1]
- count记录变化次数,i从1开始遍历不然out of range
代码
class Solution(object):
def checkPossibility(self, nums):
count = 0
for i in range(1,len(nums)):
if nums[i-1] > nums[i]:
if i-2 >= 0:
if nums[i-2] <= nums[i]:
nums[i-1] = nums[i]
else:
nums[i] = nums[i-1]
else:
nums[i-1] = nums[i]
count += 1
return count <= 1
53.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
连接:https://leetcode-cn.com/problems/maximum-subarray/
example:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
解题思路
- 贪心算法,每次都把最好的树叶(max_sum)拿在手里
- 用curr_sum去判断当前数字nums[i]是否直接大于前面数列的和,大于就替代。
- 再拿curr_sum与max_sum对比是否优于。
- 时间复杂度O(n):只遍历一次
- 空间复杂度O(1):常数空间
代码
class Solution(object):
def maxSubArray(self, nums):
curr_sum = max_sum = nums[0]
for i in range(1,len(nums)):
curr_sum = max(nums[i] , curr_sum+nums[i])
max_sum = max(curr_sum,max_sum)
return max_sum
763.划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
链接:https://leetcode-cn.com/problems/partition-labels
example:
输入: S = "ababcbacadefegdehijhklij"
输出: [9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
解题思路
- 题目解读:最多片段,且同一字母只在一个片段。等价于从左第一位a,找最后a所在位置。中间片段即为最小片段
- 难点:片段中间的其他字母会扩展区间右边界。
- 解决问题:右边界什么时候截止呢?
- 对于每一字母,去找这个字母最后一次出现位置用来更新区间右边界。遍历到右边界时,该片段就截止。
1.用字典+枚举法来更新S的最大下标:last{a:b for a,b in enumerate(S)},这段代码理解为:
last = {}
for a,b in enumerate(S):
last[b] = a
print last
》 {u'a': 8, u'c': 7, u'b': 5, u'e': 15, u'd': 14, u'g': 13, u'f': 11, u'i': 22, u'h': 19, u'k': 20, u'j': 23, u'l': 21}
2.用start,end表示当前片段首尾,若字母最大下标大于end,则扩大end=last[j],当i遍历到end时结束当前片段,进入下一个。
复杂度分析
- 时间复杂度O(N)
- 空间复杂度O(N))
代码
class Solution(object):
def partitionLabels(self, S):
last{a:b for a,b in enumerate(S)}
ans = []
start,end = 0,0
for i,j in enumerate(S):
end = max(end,last[j])
if i == end:
ans.append(i - start + 1)
start = i + 1
return ans