Given an unsorted array of integers, find the length of longest increasing subsequence.
Example:
Input:[10,9,2,5,3,7,101,18]
Output: 4 Explanation: The longest increasing subsequence is[2,3,7,101]
, therefore the length is4
.
Note:
- There may be more than one LIS combination, it is only necessary for you to return the length.
- Your algorithm should run in O(n2) complexity.
Follow up: Could you improve it to O(n log n) time complexity?
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> dp(nums.size(), 1); int res = 0; for (int i = 0; i < nums.size(); ++i) { for (int j = 0; j < i; ++j) { if (nums[i] > nums[j]) { dp[i] = max(dp[i], dp[j] + 1); } } res = max(res, dp[i]); } return res; } };
下面來看一種優化時間復雜度到 O(nlgn) 的解法,這里用到了二分查找法,所以才能加快運行時間哇。思路是,先建立一個數組 ends,把首元素放進去,然后比較之后的元素,如果遍歷到的新元素比 ends 數組中的首元素小的話,替換首元素為此新元素,如果遍歷到的新元素比 ends 數組中的末尾元素還大的話,將此新元素添加到 ends 數組末尾(注意不覆蓋原末尾元素)。如果遍歷到的新元素比 ends 數組首元素大,比尾元素小時,此時用二分查找法找到第一個不小於此新元素的位置,覆蓋掉位置的原來的數字,以此類推直至遍歷完整個 nums 數組,此時 ends 數組的長度就是要求的LIS的長度,特別注意的是 ends 數組的值可能不是一個真實的 LIS,比如若輸入數組 nums 為 {4, 2, 4, 5, 3, 7},那么算完后的 ends 數組為 {2, 3, 5, 7},可以發現它不是一個原數組的 LIS,只是長度相等而已,千萬要注意這點。參見代碼如下:
解法二:
class Solution { public: int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; vector<int> ends{nums[0]}; for (auto a : nums) { if (a < ends[0]) ends[0] = a; else if (a > ends.back()) ends.push_back(a); else { int left = 0, right = ends.size(); while (left < right) { int mid = left + (right - left) / 2; if (ends[mid] < a) left = mid + 1; else right = mid; } ends[right] = a; } } return ends.size(); } };
我們來看一種思路更清晰的二分查找法,跟上面那種方法很類似,思路是先建立一個空的 dp 數組,然后開始遍歷原數組,對於每一個遍歷到的數字,用二分查找法在 dp 數組找第一個不小於它的數字,如果這個數字不存在,那么直接在 dp 數組后面加上遍歷到的數字,如果存在,則將這個數字更新為當前遍歷到的數字,最后返回 dp 數組的長度即可,注意的是,跟上面的方法一樣,特別注意的是 dp 數組的值可能不是一個真實的 LIS。參見代碼如下:
解法三:
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> dp; for (int i = 0; i < nums.size(); ++i) { int left = 0, right = dp.size(); while (left < right) { int mid = left + (right - left) / 2; if (dp[mid] < nums[i]) left = mid + 1; else right = mid; } if (right >= dp.size()) dp.push_back(nums[i]); else dp[right] = nums[i]; } return dp.size(); } };
下面來看兩種比較 tricky 的解法,利用到了 C++ 中 STL 的 lower_bound 函數,lower_bound 返回數組中第一個不小於指定值的元素,跟上面的算法類似,還需要一個一維數組v,然后對於遍歷到的 nums 中每一個元素,找其 lower_bound,如果沒有 lower_bound,說明新元素比一維數組的尾元素還要大,直接添加到數組v中,跟解法二的思路相同了。如果有 lower_bound,說明新元素不是最大的,將其 lower_bound 替換為新元素,這個過程跟算法二的二分查找法的部分實現相同功能,最后也是返回數組v的長度,注意數組v也不一定是真實的 LIS,參見代碼如下:
解法四:
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> v; for (auto a : nums) { auto it = lower_bound(v.begin(), v.end(), a); if (it == v.end()) v.push_back(a); else *it = a; }
return v.size(); } };
既然能用 lower_bound,那么 upper_bound 就耐不住寂寞了,也要出來解個題。upper_bound 是返回數組中第一個大於指定值的元素,和 lower_bound 的區別時,它不能返回和指定值相等的元素,那么當新進來的數和數組中尾元素一樣大時,upper_bound 無法返回這個元素,那么按算法三的處理方法是加到數組中,這樣就不是嚴格的遞增子串了,所以要做個處理,在處理每個新進來的元素時,先判斷數組v中有無此元素,有的話直接跳過,這樣就避免了相同數字的情況,參見代碼如下:
解法五:
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> v; for (auto a : nums) { if (find(v.begin(), v.end(), a) != v.end()) continue; auto it = upper_bound(v.begin(), v.end(), a); if (it == v.end()) v.push_back(a); else *it = a; } return v.size(); } };
還有一種稍微復雜點的方法,參見我的另一篇博客 Longest Increasing Subsequence,那是 LintCode 上的題,但是有點不同的是,那道題讓求的 LIS 不是嚴格的遞增的,允許相同元素存在。
Github 同步地址:
https://github.com/grandyang/leetcode/issues/300
類似題目:
Increasing Triplet Subsequence
Number of Longest Increasing Subsequence
Minimum ASCII Delete Sum for Two Strings
參考資料:
https://leetcode.com/problems/longest-increasing-subsequence/