Leetcode題解系列——300. Longest Increasing Subsequence與673. Number of Longest Increasing Subsequenc(c++版)
題目連結:300.Longest Increasing Subsequence
題目大意:最長增長子序列,這是一道經典的動態規劃問題。要求輸出一串數字的最長增長子序列的長度。
增長子序列,要求nums[i] < nums[j], when i < j.
注意點:
- 使用動態規劃時,序列長度初始的值為1還是0。
- 注意判斷陣列長度為0的情況。
一.演算法設計
這裡有兩種時間複雜度的解法。
1.O( )的方法。
對於每一個數組中的值,檢視前面是否有比它小的值,如果有,則改變當前的序列長度為前面的序列長度+1,最後輸出len中最大的值,即為該陣列的最長增長子序列的長度。
由於具有n個狀態,每個狀態遷移需要O(n),故總時間複雜度為O( )。
2.O(nlogn)的方法
利用二分查詢與貪心演算法的結合來實現。由於題目不需要輸出子序列的具體序列,只需要輸出長度,故我們可以每次找遞增子序列的最後一個數的最小值放入res中,這就是貪心的思想。
每次找該長度i的最小值,保證了後面能有更多的空間查詢比這個數的較大值,故一定可以找到最長子序列。
而在查詢的時候使用二分搜尋,可以降低搜尋的時間複雜度。
故最後的總的時間複雜度為O(nlogn)
二.程式碼實現
O( )複雜度演算法示例
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( )的演算法,而不能使用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;
}
};