LIS回顾

本题是LIS的进阶版本,上周课上老师举了LIS做例子,在解答本题前,先将LIS的代码贴在这里

class Solution {public:    int lengthOfLIS(vector<int>& nums) {        auto n = (int)nums.size();        vector<int> dp(n, 1);        for (int i = 0; i < n; ++i) {            for (int j = 0; j < i; ++j) {                if (nums[j] < nums[i]) {                    dp[i] = max(dp[i], dp[j] + 1);                }            }        }        return *max_element(dp.begin(), dp.end());    }};

LIS最长回升子序列是一道经典的动静布局问题,解DP问题的关键在于定义正确的状态(有最优子结构)并找到正确的状态转移方程,在LIS中,状态转移方程为
$$LIS_j=max(LIS_i)+1,where\ 0\le i<j,\&\& \ nums[i] < nums[j] $$

最长递增子序列的个数

上面思考第637题,求最长递增子序列的个数

要求最长递增子序列的个数,首先要晓得最长子序列的长度,在LIS的代码中,DP数组代表的就是以第i个数结尾的最长子序列的长度。那么遍历一遍DP数组,咱们就能够晓得最长子序列结尾的那个数有多少种抉择,也就是

vector<int> dp;int cnt = 0;int maxlen = *max_element(dp.begin(), dp.end());for(auto d:dp){    if(d == maxlen) cnt ++;}

那么,如果咱们曾经晓得最初一位,那么倒数第二位的取值又有多少种状况呢?这启发我能够应用DFS广度优先搜寻,穷举最长LIS所有可能的状况,代码如下

class Solution {public:int cnt = 0;    int findNumberOfLIS(vector<int>& nums) {        int n = nums.size();        if (n == 0) {            return 0;        }        vector<int> dp(n, 1);        for (int i = 0; i < n; ++i) {            for (int j = 0; j < i; ++j) {                if (nums[j] < nums[i]) {                    dp[i] = max(dp[i], dp[j] + 1);                }            }        }                int maxlen = *max_element(dp.begin(), dp.end());                for(int i = 0; i < n; i++){            if(dp[i] == maxlen){//如果等于maxlen,则进行DFS                DFS(nums, dp, i);            }        }        return cnt;    }    void DFS(vector<int> &nums, vector<int> & dp, int curPos){        int len = dp[curPos];//搜寻进口        if(len == 1) {            cnt += 1;            return;        }        else{//DFS,每次找符合条件的LIS的前一位,因而要记录curPos示意以后位数            for(int i = curPos-1; i >= 0; i--){                if(dp[i] == len-1 && nums[i] < nums[curPos]){                    DFS(nums, dp, i);                }            }            return;        }    }};

不出所料,超时了,这是因为思考DFS面临的复杂度是十分高的,思考一个极其状况,有n个元素的数组,n能够被k整除,从前到后每k个数彼此相等,即$$1,1,...,1,2,2,...,2,...,n/k,n/k,...,n/k$$共\( n \),从前到后别离是\( k \)个1,\( k \)个2,\( k \)个3,...\( k \)个n/k,此时DFS的工夫复杂度为\( O((n/k)^k) \),难免会TLE。

动静布局

最长子序列的个数有最优子结构吗?能够应用DP求解吗?答案是必定的。

class Solution {public:int cnt = 0;    int findNumberOfLIS(vector<int>& nums) {        int n = nums.size();        if (n == 0) {            return 0;        }        vector<int> dp(n, 0);        vector<int> pathNum(n, 1);        for (int i = 0; i < n; ++i) {            dp[i] = 1;            pathNum[i] = 1;            for (int j = 0; j < i; ++j) {                if (nums[j] < nums[i]) {                    if(dp[j] + 1 == dp[i]){                        pathNum[i] += pathNum[j];                    }                    else if(dp[j] + 1 > dp[i]){                        pathNum[i] = pathNum[j];                        dp[i]= dp[j] + 1;                    }                }            }        }                int maxlen = *max_element(dp.begin(), dp.end());        for(int i = 0; i < n; i++){            if(dp[i] == maxlen) {                cnt += pathNum[i];            }        }        return cnt;    }};

定义\( pathNum \)数组,\( pathNum[i] \)示意所有以i结尾的最长子序列的个数,在遍历j变量时,保护pathNum数组即可。具体地,如果找到了以i结尾的更长的子序列长度,且以后以j结尾的子序列个数为\( pathNum[j] \),那么\( pathNum[i] = pathNum[j] \)即可,相当于在以j结尾的子序列前面附加一个\( nums[i] \)。如果以j结尾的子序列长度恰好比以i结尾的子序列长度小1,阐明前一位还有其余抉择,\( pathNum[i]+= pathNum[j] \)。

最初遍历\( pathNum \)数组即可。