題目:
給定一個無序的整數數組,找到其中最長上升子序列的長度。
輸入:
一個無序整數數組,
輸出:
該整數數組的最長上升子序列的長度
示例:
輸入: [10, 9, 2, 5, 3, 7, 101, 18]
輸出: 4
解釋: 最長的上升子序列是 [2, 3, 7, 101],它的長度是 4。
說明:
可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。你算法的時間複雜度應該爲 。
進階: 你能將算法的時間複雜度降低到 嗎?
思路:
分析:
- 首先仔細審題,明確題目中的條件。
子序列:不要求連續子序列,只要保證元素前後順序一致即可;
上升:這裏的“上升”是“嚴格上升”,類似於 [2, 3, 3, 6, 7] 這樣的子序列是不符合要求的; - 一個序列可能有多個最長上升子序列,題目中只要我們求這個最長的長度。如果使用回溯搜索,選擇所有的子序列進行判斷,時間複雜度爲 。
- 觀察輸入的規模,以第 i 個數字結尾的最長上升子序列長度取決於前 i 個數字,與其後的數字無關。可以使用動態規劃解決該問題,用dp數組記錄以每個數字結尾時的最長子序列的長度,即我們要求 nums[i] 必須被選取。反正一個子序列一定要以一個數字結尾,那我就將狀態這麼定義,這一點是重要且常見的。
方法一:DP解法
子問題: 以第 個元素結尾的最長子序列的長度
最優子結構: dp[i]
決策: 以第 個元素結尾
遞推關係式:
只要數組 nums 長度不爲0,則最長子序列長度最小值爲1,故將 dp 數組初始化爲1;
計算以第 i 個數字結尾的最長子序列長度 dp[i] 時,我們應該把索引是 [0, … ,i - 1] 的最長子序列長度 dp[j] 都看一遍,如果當前的數 nums[i] 大於之前的某個數nums[j],那麼 nums[i] 就可以接在 nums[j] 後形成一個更長的子序列。dp[i] 是所有 dp[j] 中的最大值加 1 (即加nums[i]本身的長度1)。
示例:
數組:
dp 的值:
時間複雜度:
空間複雜度:
實現代碼:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0) return 0;
vector<int>dp(nums.size(), 1);
int max_len = INT_MIN;
for(int i = 0; i < nums.size(); i++){
for(int j = 0; j < i; j++){
if(nums[j] < nums[i]){
dp[i] = max(dp[i], dp[j] + 1);
}
}
max_len = max_len < dp[i] ? dp[i] : max_len;
}
return max_len;
}
};
方法二:貪心 + 二分查找
進階部分,若要將算法複雜度降到,看到這個複雜度一般思路就是使用二分查找。
貪心思想:如果要使上升子序列儘可能的長,則需要讓序列上升得儘可能慢,因此希望每次在上升子序列最後加上的那個數儘可能的小。
維護一個數組dp[], dp[length]表示遞增子序列長度爲length時,序列最後的那個元素值。
初始化: dp[1]=nums[0]
遍歷nums,如果 nums[k]>dp[length],則 dp[length+1] = nums[k], length++;否則,使用二分查找,在dp[1]~dp[length]中查找從左到右第一個大於nums[k]的元素dp[j],更新dp[j]=nums[k]
時間複雜度:
空間複雜度:
實現代碼:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0) return 0;
vector<int>dp(nums.size() + 1, 0);
dp[1] = nums[0];
int max_len = 1;
for(int i = 1; i < nums.size(); i++){
if(nums[i] > dp[max_len]){
dp[++max_len] = nums[i];
}
else{
//二分查找
int left = 1, right = max_len;
int mid;
while(left < right){
mid = left + (right - left) / 2;
if(nums[i] <= dp[mid]){
right = mid;
}
else{
left = mid + 1;
}
}
dp[left] = nums[i];
}
}
return max_len;
}
};