件名のソース: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 )、その0≤j≤i 、および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)。