由于这题有很多类似题目,在此做一个总结。都采取相似的方法,即动态规划法,但时间复杂度和空间复杂度可能不是很好
300.最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
题解:
- 这种子序列求极值的题目,应该要考虑到贪心或者动态规划。这道题贪心是不可以的,我们考虑动态规划。
- 按照动态规划方法可以想出下面的定义状态的方式: 以 i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, …, n - 1
- 由于 dp[j] 中一定会包括 j,且以 j 结尾, 那么 nums[j] 一定是其所形成的序列中最大的元素,那么如果位于其后(意味着 i > j)的 nums[i] > nums[j],那么 nums[i] 一定能够融入 dp[j] 从而形成更大的序列,这个序列的长度是 dp[j] + 1。因此状态转移方程就有了:dp[i] = dp[j] + 1 (其中 i > j, nums[i] > nums[j])
代码:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n=len(nums)
if n==0:
return 0
dp=[1]*n
ans=1
for i in range(n):
for j in range(i):
if nums[i]>nums[j]:
dp[i]=max(dp[j]+1,dp[i])
ans = max(ans, dp[i])
return ans
435.无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
-
题解:
- 我们先来看下最终剩下的区间。由于剩下的区间都是不重叠的,因此剩下的相邻区间的后一个区间的开始时间一定是不小于前一个区间的结束时间的。
- 我们的目标就是删除若干区间,从而剩下最长的非严格递增子序列。这
在这里插入代码片
和上面的题目是一致的
代码:
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
n=len(intervals)
if n==0:
return 0
dp=[1]*n
ans=1
intervals.sort(key=lambda a:a[1])
for i in range(n):
for j in range(i-1,-1,-1):
if intervals[i][0]>=intervals[j][1]:
dp[i]=max(dp[j]+1,dp[i])
break
dp[i]=max(dp[i],dp[i-1])
ans=max(dp[i],ans)
return n-ans
646.最长数对链
给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。
给定一个数对集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
提示:
给出的数对的个数在 [1, 1000] 范围内。
题解
与上题类似,只是大于等于变成了大于
代码
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
n=len(pairs)
if n==0:
return 0
dp=[1]*n
ans=1
pairs.sort(key=lambda a:a[1])
for i in range(n):
for j in range(i-1,-1,-1):
if pairs[i][0]>pairs[j][1]:
dp[i]=max(dp[i],dp[j]+1)
ans=max(ans,dp[i])
return ans
452.用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数
提示:
- 0 <= points.length <= 104
- points[i].length == 2
- -231 <= xstart < xend <= 231 - 1
在这里插入代码片
解题:
与前面几个方法大致相似
代码:
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
n = len(points)
if n == 0: return 0
dp = [1] * n
cnt = 1
points.sort(key=lambda a:a[1])
for i in range(n):
for j in range(0, i):
if points[i][0] > points[j][1]:
dp[i] = max(dp[i], dp[j] + 1)
cnt = max(cnt, dp[i])
return cnt
总结:
其他的我就不一一说了。比如 673. 最长递增子序列的个数 (滴滴面试题)。 不就是求出最长序列,之后再循环比对一次就可以得出答案了么?491. 递增子序列 由于需要找到所有的递增子序列,因此动态规划就不行了,妥妥回溯就行了,套一个模板就出来了。回溯的模板可以看我之前写的回溯专题。大家把我讲的思路搞懂,这几个题一写,还怕碰到类似的题不会么?只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 最长上升子序列就是一个非常经典的基础算法,把它彻底搞懂,再去面对出题人的各种换皮就不怕了。相反,如果你不去思考题目背后的逻辑,就会刷地很痛苦。题目稍微一变化你就不会了,这也是为什么很多人说刷了很多题,但是碰到新的题目还是不会做的原因之一。
补充:关于今天的题目452题,最好的方法应该是排序加贪心来做,后面我顺便把贪心的代码放这
代码:
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if points==[]:
return 0
points.sort(key=lambda a:a[1])
end=points[0][1]
arrow=1
for i in points[1:]:
new_start=i[0]
if new_start>end:
arrow+=1
end=i[1]
return arrow