目录
这里总结一些递增子序列的相关题目,有的题目中会有最优解,但是这里主要关注的是动态规划的做法,所以只写出动态规划或者通用的一些方法。
介绍这些题目之前,先分清一下什么是子序列,什么是子串
子序列:子序列是不连续的,如序列 [4, 6, 5]
是 [1, 2, 4, 3, 7, 6, 5]
的一个子序列
子串:子串是连续的
1. 最长连续递增序列
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
这道题说的是递增序列,但是要求是连续的,所以其实是求递增子串
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return 1
dp = [1]*len(nums) #代表连续递增序列的长度
for i in range(1,len(nums)):
if nums[i]>nums[i-1]: #如果后一个元素大于前一个元素,那长度+1
dp[i] = dp[i-1]+1
return max(dp) # 找出最大的那个数就是长度,不能是dp[-1],dp[-1]代表的是以最后一个元素结尾是最长连续递增序列,但是最后一个并不一定是最大的数。
2. 最长上升子序列
使用动态规划来做,定义dp数组表示子序列的长度,dp[i] 表示在第i 个元素之前的任意一个元素为起点,到第i个元素结尾的上升子序列的长度,初始化都为1,表示每个元素都至少可以单独成为子序列,此时长度都为 1。
遍历到每个元素 nums[i] 后,再从头遍历一下[0-i] 里的这些元素nums[j],如果nums[i]>nums[j],那nums[i]这个元素就可以接到nums[j] 这个元素的后面,即 dp[i] = dp[j] + 1
所以递归方程就是 dp[i] = max(dp[i],dp[j]+1) ,最后依旧是找max(dp),而不是dp[-1]
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp=[1]*len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[i]>nums[j]:
dp[i] = max(dp[i],dp[j]+1)
return max(dp)
这道题的内层循环如果每次都从0开始遍历,其实有很多重复的遍历,所以内层循环可以使用二分法来查找,找到第一个比num[i]大的数,然后替换成num[i],具体的不多说,代码如下:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
res = []
for num in nums:
if not res or num>res[-1]:
res.append(num)
else:
l=0
r=len(res)-1
while l<r:
mid = (l+r)//2
if res[mid]<num:
l = mid+1
else:
r = mid
res[l]=num
return len(res)
3、最长递增子序列的个数
这道题和上面那道题很像,只不过多了一个统计最长递增子序列的个数
class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = [1]*len(nums)
count = [1]*len(nums)
temp = 1
res = 0
for i in range(1,len(nums)):
for j in range(i):
if nums[i]>nums[j]:
if dp[j]+1>dp[i]:
dp[i] = dp[j]+1
count[i] = count[j]
elif dp[i] == dp[j]+1:
count[i]+=count[j]
# temp = max(temp,dp[i])
temp = max(dp)
for i in range(len(nums)):
if dp[i] == temp:
res += count[i]
return res
当 dp[j]+1 > dp [i] 时,说明第一次找到了新的长度,为dp[j]+1,这个时候count[i] = count[j],表明以 nums[i]
结尾的最长递增子序列的组合方式就等于 nums[j]
目前的组合方式
当 dp[j]+1 = dp [i] 时,说明这个长度的递增序列已找到过一次了,则 count[i]+=count[j],
现有的组合方式个数加上 count [j] 的组合方式,即为总的组合方式个数
最后一次遍历是找到最长的那个长度,它所对应的count就是结果
这道题还可以用树状数组,目前能力有限,还不会树状数组,跳过
4、递增子序列
这道题不再是使用动态规划,而是深度优先搜索,类似于回溯,如果使用一般的回溯解法,耗时过慢,代码如下
class Solution:
def __init__(self):
self.res = []
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
def dfs(temp,start,nums):
if len(temp)>=2 and temp not in self.res:
self.res.append(temp[:])
for i in range(start,len(nums)):
if not temp or nums[i]>=temp[-1]:
temp.append(nums[i])
dfs(temp,i+1,nums)
temp.pop()
temp = []
dfs(temp,0,nums)
return self.res
可以看到耗时六千多毫秒,使用集合来进行剪枝依旧耗时过长,可以使用字典来剪枝。
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
def dfs(start, tmp):
dic = {}
if len(tmp) > 1:
res.append(tmp)
for i in range(start, len(nums)):
if dic.get(nums[i], 0):
continue
if len(tmp) == 0 or nums[i] >= tmp[-1]:
dic[nums[i]] = 1
dfs(i + 1, tmp + [nums[i]])
dfs(0, [])
return res
耗时一下子就降下来了。
5、最长连续序列
先空着,学习了并查集再来完善