673. 最长递增子序列的个数

给定一个未排序的整数数组,找到最长递增子序列的个数。滴滴面试

输入: [1,3,5,4,7]
输出: 2
解释:** 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

思路:这道题可以说是 300.最长上升子序列 的进阶版本

参考:

  • 确定dp数组(dp table)以及下标的含义

这道题目我们要一起维护两个数组。
dp[i]:i之前(包括i)最长递增子序列的长度为dp[i]
count[i]:以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]

  • 确定递推公式

在300.最长上升子序列 中,我们给出的状态转移是:
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
即:位置i的最长递增子序列长度 等于j从0到i-1各个位置的最长升序子序列 + 1的最大值。

本题就没那么简单了,我们要考虑两个维度,一个是dp[i]的更新,一个是count[i]的更新。
那么如何更新count[i]呢?

以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]。
1,那么在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 > dp[i],说明找到了一个更长的递增子序列。
那么以j为结尾的子串的最长递增子序列的个数,就是最新的以i为结尾的子串的最长递增子序列的个数,即:count[i] = count[j]。

2,在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 == dp[i],说明找到了两个相同长度的递增子序列。那么以i为结尾的子串的最长递增子序列的个数 就应该加上以j为结尾的子串的最长递增子序列的个数,即:count[i] += count[j];

  1. //动规,dp=以i结尾最长序列,cnt=dp的频次
  2. //时间On^2,空间On
  3. func findNumberOfLIS(nums []int) int {
  4. dp := make( []int, len(nums))
  5. cnt := make( []int, len(nums))
  6. res:= 0
  7. for i := 0 ; i < len(nums) ; i++{
  8. dp[i] = 1 //dp[i] 记录到当前节点i的最大长度,=到i节点的最大序列
  9. cnt[i] = 1 // 重复的个数 cnt = dp[i]次数,到i节点最大序列个数,即相等队列中有几种
  10. for j := 0;j < i; j++{
  11. if nums[i] > nums[j]{
  12. if dp[j] + 1 > dp[i] {
  13. dp[i] = dp[j] +1 // 更新 dp[i]放在这里,就不用max了
  14. cnt[i] = cnt[j]
  15. continue
  16. }
  17. if dp[i] == dp[j] + 1{
  18. cnt[i] += cnt[j]
  19. continue
  20. }
  21. }
  22. }
  23. res = max(dp[i], res)
  24. }
  25. sum := 0
  26. //最后还有再遍历一遍dp[i],把最长递增序列长度对应的cnt[i]累计下来就是结果了。
  27. for i := 0 ; i < len(dp) ; i++{
  28. if dp[i] == res{
  29. sum += cnt[i]
  30. }
  31. }
  32. return sum
  33. }
  34. func max( a, b int) int {
  35. if a > b {
  36. return a
  37. }
  38. return b
  39. }