[LeetCode] 300.最長増加部分列(C ++)


件名のソース:https//leetcode-cn.com/problems/longest-increasing-subsequence/

タイトル説明

整数配列numsを指定し、厳密に増加する最長のサブシーケンスの長さを見つけます。

サブシーケンスは、配列から派生したシーケンスであり、残りの要素の順序を変更せずに、配列内の要素を削除します(または削除しません)。たとえば、[3,6,2,7]は配列[0,3,1,6,2,2,7]のサブシーケンスです。

例1:
入力:nums = [10,9,2,5,3,7,101,18]
出力:4
説明:最長増加部分列は[2,3,7,101]であるため、長さは4です。
例2:
入力:nums = [0,1,0,3,2,3]
出力:4
例3:
入力:nums = [7,7,7,7,7,7,7]
出力:1
ヒント:
1 <= nums.length <= 2500
10 ^ 4 <= nums [i] <= 10 ^ 4
高度:
O(n2)時間計算量のソリューションを設計できますか?
アルゴリズムの時間計算量をO(n log(n))に減らすことができますか?

一般的なアイデア

  • 動的計画法の古典的なアプリケーションの問題は、最長の厳密に単調なサブシーケンスを見つけ、サブシーケンスは連続を必要としませんが、シーケンスがあり、貪欲+二分探索のアイデアは動的計画法によく現れます、最初に状態を定義します問題
    dp [i] = max(dp [j] + 1)、ここで0≤j≤i、およびnum [j] <num [i] dp [i] = max(dp [j] +1)、ここで0 ≤j≤i、およびnum [j] <num [i]d p [ i ]=m a x d p [ j ]+1 その0ji およびn u m [ j ] < n u m [ i ]

動的計画法O(n ^ 2)

ネストされたループが使用可能になった後、DP [j]が2の場合、インデックスjと同等であり、の長さはすでに厳密に単調なサブシーケンス2を構成し、展開時に添え字iは、この時点でのみ決定されます。 [j] <nums [i]、条件が満たされた場合、iに展開し、dp [i]を更新し、dp [j] +1がdp [i]と比較されることに注意してください。この時点で特定のサブシーケンスに含まれています

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

複雑さの分析

  • 時間計算量:O(n ^ 2)。ほぼネストされたトラバーサル配列
  • スペースの複雑さ:O(n)。nは配列のサイズです

貪欲+二分探索

  • 厳密に単調な部分列の場合、dp配列を維持できます。上記の理由により、アルゴリズムフローを取得できます。
  • 現在検出されている最長の昇順サブシーケンスの長さがlen(初期値は1)であり、配列numsが前から後ろにトラバースされるとします。nums[i]にトラバースする場合:
    • num [i]> dp [len]の場合、dp配列の最後に直接追加し、len = len +1を更新します。
    • それ以外の場合は、dp配列で二分探索し、nums [i]よりも小さい最初の数値d [k]を見つけて、d [k + 1] = nums [i]を更新します。

例として入力シーケンス[0、8、4、12、2]を取り上げます。
最初のステップは0を挿入することです、dp = [0];
2番目のステップは8を挿入することです、dp = [0、8];
3番目のステップは4を挿入することです、dp = [0、4];
4番目のステップで12を挿入します、dp = [0、4、12];
5番目のステップで2を挿入します、dp = [0、2、12]。
最後に、最大増分サブシーケンス長は3です。

class Solution {
    
    
public:
    int lengthOfLIS(vector<int>& nums) {
    
    
        int len = nums.size();
        vector<int> dp(len + 1, 0);
        int index = 1;
        dp[1] = nums[0];
        for(int i = 1 ; i < len ; ++i){
    
    
            if(dp[index] < nums[i]){
    
    
                dp[++index] = nums[i];
            }else{
    
    
                int left = 1, right = index, result = -1;
                while(left <= right){
    
    
                    int mid = right + ((left - right) >> 1);
                    if(dp[mid] < nums[i])  left = mid + 1;
                    else{
    
    
                        if(mid == 0 || dp[mid - 1] < nums[i]){
    
    
                            result = mid;
                            break;
                        }
                        else    right = mid - 1;
                    }
                }
                dp[(result != -1) ? result : 1] = nums[i];
            }
        }
        return index;
    }
};

複雑さの分析

  • 時間計算量:O(nlogn)。二分探索の平均時間計算量はO(nlogn)です。ここで、nは最悪の場合配列nに等しくなります。
  • スペースの複雑さ:O(n)。

おすすめ

転載: blog.csdn.net/lr_shadow/article/details/114366278