Leetcode题解系列——300. Longest Increasing Subsequence与673. Number of Longest Increasing Subsequenc(c++版)

版权声明: https://blog.csdn.net/dickdick111/article/details/83512341

题目链接:300.Longest Increasing Subsequence

题目大意:最长增长子序列,这是一道经典的动态规划问题。要求输出一串数字的最长增长子序列的长度。

增长子序列,要求nums[i] < nums[j], when i < j.

注意点:

  1. 使用动态规划时,序列长度初始的值为1还是0。
  2. 注意判断数组长度为0的情况。

一.算法设计

这里有两种时间复杂度的解法。

1.O( n 2 n^2 )的方法。

对于每一个数组中的值,查看前面是否有比它小的值,如果有,则改变当前的序列长度为前面的序列长度+1,最后输出len中最大的值,即为该数组的最长增长子序列的长度。

由于具有n个状态,每个状态迁移需要O(n),故总时间复杂度为O( n 2 n^2 )。

2.O(nlogn)的方法

利用二分查找与贪心算法的结合来实现。由于题目不需要输出子序列的具体序列,只需要输出长度,故我们可以每次找递增子序列的最后一个数的最小值放入res中,这就是贪心的思想。

每次找该长度i的最小值,保证了后面能有更多的空间查找比这个数的较大值,故一定可以找到最长子序列。

而在查找的时候使用二分搜索,可以降低搜索的时间复杂度。

故最后的总的时间复杂度为O(nlogn)

二.代码实现

O( n 2 n^2 )复杂度算法示例

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.empty()) return 0;
        int _size = nums.size();
        int count = 1;
        int len[10000] = {0};
        //设置所有序列长度的初始值为1
        fill(len,len+_size,1);
        for(int i = 1; i < _size; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    len[i] = max(len[i], len[j]+1);
                }
            }
            count = max(count,len[i]);
        }
        return count;
    }
};

O(nlogn)复杂度算法示例

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> res;
        for(int num:nums){
        	// 二分查找res中是否有比num还要大于等于的数
            auto it = lower_bound(res.begin(), res.end(), num);
            // 若没有找到,则是第一次访问到最长序列的最后元素
            if(it == res.end()){
                res.push_back(num);
            }
            //若找到了,则将小的num值赋予找到的下标元素
            else{
                *it = num;
            }
        }
        // 最后,res所保存的是最长序列的最后一个数的最小值
        // 而size就是最长序列的长度
        return res.size();
    }
};

三.算法变形

通过这一算法,我们还可以算出最长子序列的个数。

题目链接:673. Number of Longest Increasing Subsequence

这时候,我们要利用第一种O( n 2 n^2 )的算法,而不能使用O(nlogn)的算法,因为O(nlogn)的算法仅仅能找出最长子序列的长度,以及保存每一个位置的最小值,而不能找出所有子串。

统计有多少个最长字串,即找出每一个位置能有多少个数字可以扩展

所以可以定义状态ans[i]表示,当前i位置,达到最长递增子序列的方法个数

如果下一个结点不是第一次到达该长度,说明有新的路径产生
ans[i] += ans[j]
如果该节点为第一次到达最长长度,之前的ans[i]必须赋值回1,即只有一条路能到达
ans[i] = ans[j]

最后,只需要所有等价于最大子序列长度的最后一个元素下标,通过这些最后数字的下标的ans值叠加来得到最长子序列的个数。

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
       if(nums.empty()) return 0;
        int _size = nums.size();
        int max_len = 1;
        int count = 0;
        int len[10000] = {0};
        int ans[10000] = {0};
        fill(ans,ans+_size,1);
        fill(len,len+_size,1);
        for(int i = 1; i < _size; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    //不是第一次到达
                    if(len[i] == len[j]+1){
                        ans[i] += ans[j];
                    }
                    //第一次到达
                    else if(len[i] < len[j]+1){
                        len[i] = len[j]+1;
                        ans[i] = ans[j];
                    }
                }
            }
            max_len = max(max_len,len[i]);
        }
        
        for(int i = 0; i < _size; i++){
            if(len[i] == max_len) count += ans[i];
        }
        return count;
    }
};

猜你喜欢

转载自blog.csdn.net/dickdick111/article/details/83512341