力扣:673. 最长递增子序列的个数
Posted 战场小包
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣:673. 最长递增子序列的个数相关的知识,希望对你有一定的参考价值。
题源:最长递增子序列的个数
做这个题之前可以顺路复习一下LIS最长递增子序列问题。
1. LIS
题源:最长递增子序列
最长递增子序列有两种做法;O(n2)的动态规划和O(nlogn)的贪心+二分。
1.1 动态规划
定义dp[i]为以第i个元素为结尾的最长上升子序列长度,其中num[i]必须被选择,那么状态转移方程为(dp初始化值为1):
dp[i] = max(dp[j]) + 1; j < i, num[i] > num[j]
def findNumberOfLIS(nums):
dp = [1] * len(nums)
for i in range(1, len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
复杂度分析:
- 时间复杂度: O(n2)
- 空间复杂度: O(n) dp[n]借助的空间
1.2 贪心 + 二分查找
我们要求得最长的上升子序列,那么我们应该保证序列上升的尽可能慢,因此每次添加到子序列后的数尽可能地小。
基于上面的想法,维护一个数组d[i],表示长度为i的最长上升子序列,且d[i]为满足长度为i的上升子序列的最小值,使用len记录数组d的元素个数,初始值d[0] = num[0]。
算法流程:
- 遍历数组nums,当遍历到nums[i]时:
- 若nums[i] > d[len],直接加入到数组末尾,len+1
- 否则,在d数组中寻找第一个大于nums[i]的位置,将其插入
- 以序列[0,2,5,3,7,101,18]
- 第一步插入0:d = [0]
- 第二步插入2:d = [0,2]
- 第三步插入5:d = [0,2,5]
- 第四步更新长度为3的最长子序列的末尾值:d = [0,2,3]
- 第五步插入7:d = [0,2,3,7]
- 第六步插入101:d = [0,2,3,7,101]
- 第四步更新长度为5的最长子序列的末尾值:d = [0,2,3,7,18],得到最长递增子序列长度为5
def findNumberOfLIS(nums):
d = []
for n in nums:
if not d or n > d[-1]:
d.append(n)
else:
left, right = 0, len(d) - 1
while left < right:
mid = left + (right - left) // 2
if d[mid] < n:
left = mid + 1
else:
right = mid
d[left] = n
return len(d)
复杂度分析:
- 时间复杂度: O(nlogn) 使用了二分查找
- 空间复杂度: O(n) d[n]借助的空间
2. 最长递增子序列的个数
2.1 动态规划
在LIS的基础上做进一步的求解:
定义dp[i]为以第i个元素为结尾的最长上升子序列长度,freq[i]为以第i个元素为结尾的最长上升子序列的个数。若nums的最长子序列长度为maxLen,那么答案为所有满足dp[i]=maxLen所对应的freq[i]之和。
dp[i] = max(dp[j]) + 1; j < i, num[i] > num[j]
freq[i]为所有满足dp[i]=dp[j] + 1的freq[j]之和
def findNumberOfLIS(nums):
maxLen = 1
freq = [1] * len(nums)
dp = [1] * len(nums)
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
freq[i] = freq[j]
elif dp[j] + 1 == dp[i]:
freq[i] += freq[j]
maxLen = max(maxLen, dp[i])
sumFreq = 0
for i in range(nums):
if dp[i] == maxLen:
sumFreq += freq[i]
return sumFreq
复杂度分析:
- 时间复杂度: O(n2)
- 空间复杂度: O(n) dp[n]借助的空间
以上是关于力扣:673. 最长递增子序列的个数的主要内容,如果未能解决你的问题,请参考以下文章