赞
踩
给定一个未排序的整数数组 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
- class Solution {
- // 时间复杂度O(N*logN)
- public int findNumberOfLIS(int[] nums) {
- // 过滤无效参数
- if (nums == null || nums.length == 0) {
- return 0;
- }
- // 设置dp,里面存储了所有长度的递增子序列中以每一个nums[i]为结尾的子序列个数分别有多少个
- // dp.get(i):存储长度为i的递增子序列信息
- // dp.get(i).(key, value):表示以大于等于key结尾的长度为i的递增子序列的个数为value个
- ArrayList<TreeMap<Integer, Integer>> dp = new ArrayList<>();
- // 遍历nums
- for (int num : nums) {
- // 记录以num结尾的递增子序列的最大长度
- int len = 0;
- // 记录以num结尾的最大递增子序列的个数
- int cnt = 0;
-
- // 1、找到以num结尾的最长递增子序列的长度
- // 用二分法找到dp中找到一个长度最小的无法和num构成递增子序列的长度,即找到一种长度的最小结尾数是大于等于num的,并且这个长度是满足这个条件中最小的长度
- len = search(dp, num);
-
- // 2、计算以num结尾的最长递增子序列的个数
- // 如果len == 0,说明此时num比所有的数都小,无法和他们组成递增子序列,只能自己组成一个子序列,个数为1个
- if (len == 0) {
- cnt = 1;
- // num可以和以前的子序列组成更长的递增子序列
- } else {
- // 获取这个可以和num组成递增子序列的最长递增子序列信息
- TreeMap<Integer, Integer> treeMap = dp.get(len - 1);
- // 以num结尾的最长递增子序列个数就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
- // treeMap.firstEntry().getValue():长度为len-1的递增子序列总数
- // treeMap.ceilingEntry(num).getValue():长度为len-1的递增子序列中结尾值大于num的子序列总数
- // 上面两个数相减得到的就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
- cnt = treeMap.firstEntry().getValue() - (treeMap.ceilingEntry(num) == null ? 0 : treeMap.ceilingEntry(num).getValue());
- }
-
- // 3、将计算得到的len和cnt加入到dp中
- // 如果len == dp.size(),说明以num结尾的最长递增子序列已经推高了当前找到的最长递增子序列长度,需要新开一个TreeMap来存储
- if (len == dp.size()) {
- dp.add(new TreeMap<Integer, Integer>());
- // 将以num结尾的最长递增子序列数据加入到dp中
- dp.get(len).put(num, cnt);
- // 服用以前已有的TreeMap来存储以num结尾的最长递增子序列数据
- } else {
- // 这里要注意,len长度的递增子序列中,此时num一定是最小结尾
- // 因为前面通过二分法找到的len长度是长度最小的无法和num构成递增子序列的长度,即len长度的递增子序列原本的最小结尾数是大于num的,
- // 所以num的加入一定会作为len长度的递增子序列中新的最小结尾数
- // 所以在计算长度等于len的递增子序列中结尾数大于等于num的个数时,就直接用dp.get(len).firstEntry().getValue() + cnt
- // dp.get(len).firstEntry().getValue():原本就在TreeMap中结尾数大于等于num的总数,它也是原来的长度为len的递增子序列的总个数
- dp.get(len).put(num, dp.get(len).firstEntry().getValue() + cnt);
- }
- }
- // 直接返回dp中最大长度的递增子序列总个数即可
- // 最大长度:dp.size() - 1
- // 递增子序列总个数:dp.get(dp.size() - 1).firstEntry().getValue()
- return dp.get(dp.size() - 1).firstEntry().getValue();
- }
-
- // 二分查找,返回TreeMap.firstEntry().getKey()>=num最左的位置
- public int search(ArrayList<TreeMap<Integer, Integer>> dp, int num) {
- int l = 0;
- int r = dp.size() - 1;
- // 如果最后返回dp.size()就说明此时dp中没有比num大的
- int ans = dp.size();
- while (l <= r) {
- int m = (l + r) >> 1;
- // TreeMap.firstEntry()就是当前这个长度的递增子序列中结尾数最小的子序列
- if (dp.get(m).firstEntry().getKey() >= num) {
- // 记录大于等于num的答案
- ans = m;
- r = m - 1;
- } else {
- // 如果一直执行这个分支就会返回0,说明num比当前所有的数都小
- l = m + 1;
- }
- }
- return ans;
- }
-
- }

假设遍历到了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个。
至此就完成了整个流程的处理,当把所有数都遍历完一遍后,记录的结构中最大长度的总子序列个数(即记录的大于等于最小结位值位置的个数)就是最后的最长递增子序列个数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。