文章目录
1、数对可以组成的最长链
646. 最长数对链(Medium)
方法一:贪心算法
对所有的数对按照第二个数的大小进行排序。第一步选结束数字最小的那个数对。 然后,每步都选和上一个选中的数对不冲突且结束数字最小的那个数。
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
pairs.sort(key=lambda x: (x[1], x[0]))
res = 1
tail = pairs[0][1]
for _, item in enumerate(pairs):
if item[0] > tail:
tail = item[1]
res += 1
return res
方法二:动态规划
对所有的数对按照第一个数的大小进行排序。子问题为数对 pairs[i](i=1, 2, 3…N)为终点的最长链。a[i] 表示已数对 pairs[i]为终点的链长度。
- 初始状态:a[i] = 1
- a[i] = max { a[i], a[j]+1} 0<= j< i 且 pairs[i][0]>pairs[j][1]
再寻找 a[i] 的最大值。
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
pairs.sort()
dp = [1] * len(pairs)
for i in range(len(pairs)):
for j in range(i):
if pairs[i][0] > pairs[j][1]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
2、分配饼干
455. 分发饼干(Easy)
方法:排序 + 贪心
为了不浪费饼干,应该给每个孩子分配可以使该孩子满足,且大小最小的饼干。
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
i = j = res = 0
len_g, len_s = len(g), len(s)
while i < len_g and j < len_s:
if s[j] >= g[i]:
res += 1
i += 1
j += 1
return res
3、不重叠的区间个数
435. 无重叠区间(Medium)
贪心思想,计算不重复的区间最多多少个,然后用区间总个数减去不重叠区间的个数。将区间排序按照结尾数字排序,这次选的结尾越小,留给后面的区间的空间越大。每次选结尾最小且与前面不重叠的那个区间。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x:(x[1], x[0]))
tail = intervals[0][1]
res = 1
for _, item in enumerate(intervals):
if item[0] >= tail:
res += 1
tail = item[1]
return len(intervals)-res
4、最少需要多少飞镖刺破气球
452. 用最少数量的箭引爆气球(Medium)
方法:排序 + 贪心
这里是计算不重叠的区间个数一共有多少个,按照每个区间的结束坐标排序,选取第一个区间,以后每次选取与当前区间不重叠且结束坐标最小的区间。
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort(key=lambda x:(x[1], x[0]))
tail = points[0][1]
res = 1
for _, item in enumerate(points):
if item[0] > tail:
tail = item[1]
res += 1
return res
5、根据身高序号重排序
406. 根据身高重建队列(Medium)
按照身高降序排序,身高相等的按序号升序排序,遍历所有的人,将每个人插到对应位置上。
当放入第 i 个人时:
- 第 0,⋯,i−1 个人已经在队列中被安排了位置,他们只要站在第 i 个人的前面,就会对第 i 个人产生影响,因为他们都比第 i 个人高;
- 而第 i+1,⋯,n−1 个人还没有被放入队列中,并且他们无论站在哪里,对第 i 个人都没有任何影响,因为他们都比第 i 个人矮。
后面的人既然不会对第 i 个人造成影响,可以采用插空的方法,当我们放入第 i 个人时,将其插入队列中,使得他的前面恰好有 ki 个人即可
举例说明:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
排序:
[7, 0] [7, 1] [6, 1] [5, 0] [5, 2] [4, 4]
插入:
[7, 0]
[7, 0] [7, 1]
[7, 0] [6, 1] [7, 1]
[5, 0] [7, 0] [6, 1] [7, 1]
[5, 0] [7, 0] 5, 2] [6, 1] [7, 1]
[5, 0] [7, 0] 5, 2] [6, 1] [4, 4] [7, 1]
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key=lambda x: (-x[0], x[1]))
ans = list()
for item in people:
if len(ans) <= item[1]:
ans.append(item)
else:
ans.insert(item[1], item)
return ans
下面的写法也可以实现插入效果
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key=lambda x: (-x[0], x[1]))
ans = list()
for item in people:
ans[item[1]:item[1]] = [item]
return ans
6、买卖股票最大的收益
121. 买卖股票的最佳时机(Easy)
记录前面的最小价格,将这个价格作为买入价格,当前价格作为卖出价格,当前利润大于前面的利润,更新利润。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
small_price = prices[0]
res = 0
for _, item in enumerate(prices):
small_price = min(small_price, item)
res = max(res, item-small_price)
return res
7、买卖股票最大的收益Ⅱ
122. 买卖股票的最佳时机 II(Medium)
只要价格在增加,就有收益。
对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
res = 0
n = len(prices)
for i in range(1, n):
if prices[i] > prices[i-1]:
res += prices[i] - prices[i-1]
return res
8、种植花朵
605. 种花问题(Easy)
首先将数组变成 [1, 0] + flowerbed + [0, 1],这样就不需要考虑收尾的问题了
下标 i,j 有花,之间是空地,则之间还能种 (j-i-2)//2 朵花。维护 pre_flower 表示上一朵已经种植的花的下标位置。从左往右遍历数组 flowerbed,当遇到 flowerbed[i]=1 时根据 pre_flower 和 i 的值计算上一个区间内可以种植花的最多数量,并更新pre_flower。最后判断整个花坛内可以种入的花的最多数量是否大于等于 n。
在循环中当可以种植的花朵已经大于等于n,可以直接跳出循环。
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
flowerbed.insert(0, 0)
flowerbed.insert(0, 1)
flowerbed.append(0)
flowerbed.append(1)
pre_flower, res = 0, 0
for i, item in enumerate(flowerbed):
if i > 0 and item:
res += (i-pre_flower-2)//2
if res >= n:
return True
pre_flower = i
return res >= n
9、判断是否是字串
392. 判断子序列(Easy)
方法:双指针法
初始化两个下标 i 和 j,分别指向 s 和 t 的初始位置。匹配成功则同时右移,匹配失败则 j 右移,i 不变。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
s_len = len(s)
t_len = len(t)
i, j = 0, 0
while i < s_len and j < t_len:
if s[i] == t[j]:
i += 1
j += 1
return i >= s_len
10、修改一个数成为非递减数组
665. 非递减数列(Medium)
遍历数组,如果遇到递减:
还能修改:
修改方案1:将nums[i]缩小至nums[i + 1];
修改方案2:将nums[i + 1]放大至nums[i];
不能修改了:直接返回false;
class Solution:
def checkPossibility(self, nums: List[int]) -> bool:
n = len(nums)
if n <=1: return True
flag = True if nums[0] <= nums[1] else False
for i in range(1, n-1):
if nums[i] > nums[i+1]:
if flag:
flag = False
if nums[i+1] >= nums[i-1]:
nums[i] = nums[i+1]
else:
nums[i+1] = nums[i]
else:
return flag
return True
11、连续子数组的最大和
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
res, cur = nums[0], 0
n = len(nums)
for _, item in enumerate(nums):
cur += item
res = max(res, cur)6785
cur = cur if cur > 0 else 0
return res
12、分隔字符串使同种字符出现在一起
763. 划分字母区间(Medium)
首先统计字符串中每种字符最后一次出现的位置。从前向后遍历字符串种每一个字符,查看这些字符最后一次出现的位置,当前位置等于前面这些字符最后一次出现的位置时,说明前面查看的这些字符在后面的字符中不会出现,可以在这里将字符串分隔开。
class Solution:
def partitionLabels(self, s: str) -> List[int]:
last = [0] * 26
for i, c in enumerate(s):
last[ord(c) - ord('a')] = i
begin, end = 0, 0
res = []
for i, c in enumerate(s):
end = max(end, last[ord(c) - ord('a')])
if end == i:
res.append(i - begin + 1)
begin = i + 1
return res
13、最小差值 II
我们把原数组先排一下序,然题目要求每个元素要么向上移动 K 的距离,要么向下移动 K 的距离,然后要求这个新数组的“最大最小值的距离尽可能地小”。将数组拆成两半,把左边那一半上移,把右边那一半下移,如下图所示。黑色是原数组,红色是新数组:
在 i 这一点分割数组,A[0] ~ A[i] 元素都上移,A[i + 1] ~ A[A.length - 1] 的元素都下移。
此时 B 点的值是 A[i] + K,D 点的值是 A[A.length - 1] - K。
新数组的最大值要么是 B 点要么是 D 点,也就是说新数组的最大值是 Max(A[i] + K, A[A.length - 1] - K)。
同样道理,此时 A 点的值是 A[0] + K,C 点的值是 A[i + 1] - K。
新数组的最小值要么是 A 点要么是 C 点,也就是说新数组的最小值是 Min(A[0] + K, A[i + 1] - K)。
题目需要的“新数组的最大值和最小值的差值”,就是 Max(A[i] + K, A[A.length - 1] - K) - Min(A[0] + K, A[i + 1] - K)。让 i 从 0 到 A.length - 2 挨个遍历,取上面算式的最小值即可。
class Solution(object):
def smallestRangeII(self, A, K):
len_A = len(A)
A = sorted(A)
res = A[len_A-1] - A[0]
for i in range(0, len_A-1):
max_num = max(A[i]+K, A[len_A-1]-K)
min_num = min(A[0]+K, A[i+1]-K)
res = min(res, max_num-min_num)
return res
14、摆动序列
当序列中有一段连续的递增(或递减)时,为形成摇摆子序列,我们只需要保留这段连续的递增(或递减)的首位元素,这样更可能使得尾部的后一个元素成为摇摆子序列的下一个元素。
class Solution(object):
def wiggleMaxLength(self, nums):
len_num = len(nums)
if len_num <= 1:
return len_num
pre_diff = nums[1] - nums[0]
res = 1 if pre_diff == 0 else 2
for i in range(2, len_num):
diff = nums[i] - nums[i-1]
if (diff < 0 and pre_diff >= 0) or (diff > 0 and pre_diff <= 0):
res += 1
pre_diff = diff
return res
时间复杂度: O(n)
空间复杂度: O(1)
15、发放糖果
135. 分发糖果(Hard)
思路:两次遍历
把所有孩子的糖果数初始化为 1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1
class Solution:
def candy(self, ratings: List[int]) -> int:
n = len(ratings)
num = [1] * n
for i in range(n - 1):
if ratings[i + 1] > ratings[i]:
num[i + 1] = num[i] + 1
for i in range(n - 1, 0, -1):
if ratings[i - 1] > ratings[i] and num[i - 1] <= num[i]:
num[i - 1] = num[i] + 1
return sum(num)
时间复杂度: O(n)
空间复杂度: O(n)