当前位置:   article > 正文

【LeetCode】最长递增子序列的个数 [M](动态规划)_给定一个长度为 的序列,求有多少个子序列满足长度为 ,并且递增。答案对 取模。

给定一个长度为 的序列,求有多少个子序列满足长度为 ,并且递增。答案对 取模。

673. 最长递增子序列的个数 - 力扣(LeetCode)

一、题目

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

注意 这个数列必须是 严格 递增的。

示例 1:

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

示例 2:​​​​​​​

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

提示:

  • 1 <= nums.length <= 2000
  • -106 <= nums[i] <= 106

二、代码

  1. class Solution {
  2. // 时间复杂度O(N*logN)
  3. public int findNumberOfLIS(int[] nums) {
  4. // 过滤无效参数
  5. if (nums == null || nums.length == 0) {
  6. return 0;
  7. }
  8. // 设置dp,里面存储了所有长度的递增子序列中以每一个nums[i]为结尾的子序列个数分别有多少个
  9. // dp.get(i):存储长度为i的递增子序列信息
  10. // dp.get(i).(key, value):表示以大于等于key结尾的长度为i的递增子序列的个数为value个
  11. ArrayList<TreeMap<Integer, Integer>> dp = new ArrayList<>();
  12. // 遍历nums
  13. for (int num : nums) {
  14. // 记录以num结尾的递增子序列的最大长度
  15. int len = 0;
  16. // 记录以num结尾的最大递增子序列的个数
  17. int cnt = 0;
  18. // 1、找到以num结尾的最长递增子序列的长度
  19. // 用二分法找到dp中找到一个长度最小的无法和num构成递增子序列的长度,即找到一种长度的最小结尾数是大于等于num的,并且这个长度是满足这个条件中最小的长度
  20. len = search(dp, num);
  21. // 2、计算以num结尾的最长递增子序列的个数
  22. // 如果len == 0,说明此时num比所有的数都小,无法和他们组成递增子序列,只能自己组成一个子序列,个数为1个
  23. if (len == 0) {
  24. cnt = 1;
  25. // num可以和以前的子序列组成更长的递增子序列
  26. } else {
  27. // 获取这个可以和num组成递增子序列的最长递增子序列信息
  28. TreeMap<Integer, Integer> treeMap = dp.get(len - 1);
  29. // 以num结尾的最长递增子序列个数就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
  30. // treeMap.firstEntry().getValue():长度为len-1的递增子序列总数
  31. // treeMap.ceilingEntry(num).getValue():长度为len-1的递增子序列中结尾值大于num的子序列总数
  32. // 上面两个数相减得到的就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
  33. cnt = treeMap.firstEntry().getValue() - (treeMap.ceilingEntry(num) == null ? 0 : treeMap.ceilingEntry(num).getValue());
  34. }
  35. // 3、将计算得到的len和cnt加入到dp中
  36. // 如果len == dp.size(),说明以num结尾的最长递增子序列已经推高了当前找到的最长递增子序列长度,需要新开一个TreeMap来存储
  37. if (len == dp.size()) {
  38. dp.add(new TreeMap<Integer, Integer>());
  39. // 将以num结尾的最长递增子序列数据加入到dp中
  40. dp.get(len).put(num, cnt);
  41. // 服用以前已有的TreeMap来存储以num结尾的最长递增子序列数据
  42. } else {
  43. // 这里要注意,len长度的递增子序列中,此时num一定是最小结尾
  44. // 因为前面通过二分法找到的len长度是长度最小的无法和num构成递增子序列的长度,即len长度的递增子序列原本的最小结尾数是大于num的,
  45. // 所以num的加入一定会作为len长度的递增子序列中新的最小结尾数
  46. // 所以在计算长度等于len的递增子序列中结尾数大于等于num的个数时,就直接用dp.get(len).firstEntry().getValue() + cnt
  47. // dp.get(len).firstEntry().getValue():原本就在TreeMap中结尾数大于等于num的总数,它也是原来的长度为len的递增子序列的总个数
  48. dp.get(len).put(num, dp.get(len).firstEntry().getValue() + cnt);
  49. }
  50. }
  51. // 直接返回dp中最大长度的递增子序列总个数即可
  52. // 最大长度:dp.size() - 1
  53. // 递增子序列总个数:dp.get(dp.size() - 1).firstEntry().getValue()
  54. return dp.get(dp.size() - 1).firstEntry().getValue();
  55. }
  56. // 二分查找,返回TreeMap.firstEntry().getKey()>=num最左的位置
  57. public int search(ArrayList<TreeMap<Integer, Integer>> dp, int num) {
  58. int l = 0;
  59. int r = dp.size() - 1;
  60. // 如果最后返回dp.size()就说明此时dp中没有比num大的
  61. int ans = dp.size();
  62. while (l <= r) {
  63. int m = (l + r) >> 1;
  64. // TreeMap.firstEntry()就是当前这个长度的递增子序列中结尾数最小的子序列
  65. if (dp.get(m).firstEntry().getKey() >= num) {
  66. // 记录大于等于num的答案
  67. ans = m;
  68. r = m - 1;
  69. } else {
  70. // 如果一直执行这个分支就会返回0,说明num比当前所有的数都小
  71. l = m + 1;
  72. }
  73. }
  74. return ans;
  75. }
  76. }

三、解题思路 

假设遍历到了x这个数,先通过二分法去找x能组成的最长长度的递增子序列是多少,假设它最长能组成Y长度,这是因为Y-1长度的最小结尾数小于x,但是Y长度的最小结尾数大于x,所以x最多就是和Y-1长度的子序列组成递增子序列。

然后利用有序表找到Y-1长度中结尾数大于x的最小值是多少,假设是z,找这个位置记录的个数,假设为c个,假设Y-1长度的子序列总数为a(即这个长度记录中大于等于结尾值最小的那个个数),然后用Y-1长度递增子序列的总数量a减去c,得到的就是Y-1长度中结尾小于x的个数一共有a-c个,也就是能组成的长度为Y以x结尾的递增子序列的个数为a-c个。

然后长度为Y的记录中大于x最近的值是k,k的记录是d个,表示长度为Y结尾大于等于k的子序列有d个,那么记录在大于等于x的个数就是a-c+d个。

至此就完成了整个流程的处理,当把所有数都遍历完一遍后,记录的结构中最大长度的总子序列个数(即记录的大于等于最小结位值位置的个数)就是最后的最长递增子序列个数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/521473
推荐阅读
相关标签
  

闽ICP备14008679号