Leetcode:300.最長上升子序列
給定一個無序的整數陣列,找到其中最長上升子序列的長度。
示例:
輸入:[10,9,2,5,3,7,101,18]
輸出: 4 解釋: 最長的上升子序列是[2,3,7,101],
它的長度是4
。
說明:
- 可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
- 你演算法的時間複雜度應該為 O(n2) 。
進階: 你能將演算法的時間複雜度降低到 O(n log n) 嗎?
解題思路:
1. 普通演算法
動態規劃。假設陣列的前n個數的所有上升子序列是vector<vector<int>> data。第n+1個數將會對data產生影響。1. 如果data[i-1].back()<nums[n+1],那麼將nums[n+1]加入陣列末尾。2.由於最終的結果未必包含nums[n+1],於是當加入nums[n+1]時需要將data[i-1]複製一份加入data。這樣表面上解決了問題,但是實際上如果每次都需要將nums[n+1]加入data中所有序列的後端(nums升序),那麼data的規模將指數上升,明顯是不可取的。
後來一想,將nums[n+1]加入data的所有可能序列的末端,這個操作有很大的改進空間,因為nums[n+1]出現之後,以nums[n+1]結尾的最長升序是確定的而且是唯一的,取決於data中升序列末端小於nums[n+1]的最長一個,按照這個想法就可以很好地解決上述的記憶體不足的現象。
很顯然data這個結構已經不適用了,改用可以自動排序的雜湊容器map<int,int>mp。其中容器的key代表數字nums,val代表當前以數字nums結尾出現的最長升序列。當遇到nums[n+1]時,只需訪問key比nums[n+1]小的pair,在這些key中找一個val最大的,然後當前nums[n+1]結尾的最大升序長度即是val+1。如果沒有比nums[n+1]跟小的key,令nums[n+1]=1。如此,mp.size()<=nums.size(),取決於nums中不重複元素的個數,空間複雜度O(n)。時間複雜度O(n)-O(n^2)之間。已經滿足了題目要求。
事實上,這個演算法測試時間24ms,擊敗48%,仍有改進的空間。
2. 高階演算法
動態規劃,二分查詢。假設vector<int> data是前n個數查詢得到的最長升序列。當nums[n+1]出現時,可能有以下三種情況:
- nums[n+1]是data中的一個數,那麼nums[n+1]不會影響原有的升序列。
- nums[n+1]恰好小於data[i],那麼data[i]=nums[n+1]。原有的升序列長度不變,只需要理解為什麼更新data[i]的值。我們假設最終結果裡n+1之後升序列是n_val,如果滿足(data[i],n_val)是升序,那麼(nums[n+1],n_val)也必然是升序,這個時候講nums[n+1]替換data[i]這一步是否存在不重要,可有可無,因為求解的是長度。如果不滿足(data[i],n_val)是升序,那麼,必須替將data[i]換nums[n+1],(nums[n+1],n_val)才是升序。綜上,應該替換,但是由此可知,這這麼一來,想要求解升序列是什麼就難
- 剩下只可能是nums[n+1]大於data中的最後一個數,在末尾追加即可。
- 以上的查詢,都用二分查詢,特殊之處在於返回1,2,3三種情況。
這樣一來時間複雜度就成了O(n)-O(nlogn),空間複雜度為O(n)。
普通解法:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int size = nums.size(); if (size <= 1) return size; map<int, int> mp = { {nums[0],1} }; int i; map<int, int>::iterator it; for (i = 2; i <= size; i++) { int max = 0;//獲取nums[i-1]的最長序列 bool sgn = false; for (it = mp.begin(); it != mp.end(); it++) { if (it->first < nums[i - 1]) { sgn = true; max = (it->second + 1>max ? it->second + 1 : max); } else break; } if (sgn == false) { mp[nums[i - 1]] = 1; } else mp[nums[i - 1]] = max; } int res=0; for (it = mp.begin(); it != mp.end(); it++) { res = (res > it->second ? res : it->second); } return res; } }; |
參考的高階解法:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int size = nums.size(); vector<int> dp; for (int i = 0; i < size; i++) { int index = binarySearch(dp, nums[i]); if (index == dp.size()) { dp.push_back(nums[i]); } else if (dp[index] > nums[i]) { dp[index] = nums[i]; } } return dp.size(); } int binarySearch(const vector<int>& nums, int target) { int low = 0, high = nums.size(); while (low < high) { int mid = low + (high - low) / 2; if (nums[mid] == target) return mid; else if (nums[mid] < target) low = mid + 1; else high = mid; } return low; } }; |