【LeetCode】300.最長遞增子序列——暴力遞迴(O(n^3)),動態規劃(O(n^2)),動態規劃+二分法(O(nlogn))
阿新 • • 發佈:2021-10-03
演算法新手,刷力扣遇到這題,搞了半天終於搞懂了,來這記錄一下,歡迎大家交流指點。
題目描述:
給你一個整數陣列 nums ,找到其中最長嚴格遞增子序列的長度。
子序列是由陣列派生而來的序列,刪除(或不刪除)陣列中的元素而不改變其餘元素的順序。例如,[3,6,2,7] 是陣列 [0,3,1,6,2,2,7] 的子序列。
解法一:暴力遞迴
不解釋,先暴力搞一下。(時間複雜度O(n^3),不行)
1 class Solution { 2 public: 3 int l(vector<int>& nums) { // 返回以nums[0]開頭的最長遞增序列長度4 if (nums.size() < 2) 5 return nums.size(); 6 int max_len = 1; 7 for (int i = 1; i < nums.size(); ++ i) 8 if (nums[i] > nums[0]) { 9 vector<int> t{nums.begin() + i, nums.end()}; 10 max_len = max(max_len, l(t) + 1); 11 } 12 return max_len; 13 } 14 int lengthOfLIS(vector<int>& nums) { // 所有序列遍歷一遍 15 int max_len = 1; 16 for (i = 0; i < nums.size(); ++ i) { 17 vector<int> t(nums.begin() + i, nums.end()); 18 max_len = max(max_len, l(t));19 } 20 return max_len; 21 } 22 };
小優化一下,記憶化搜尋。(還是不行,時間複雜度還是太高)
1 class Solution { 2 public: 3 int l(unordered_map<int, int>& map, vector<int>& nums) { 4 if (nums.size() < 2) 5 return nums.size(); 6 if (map.find(nums[0]) != map.end()) // 如果已經知道了以某個數開頭的元素的最長序列數,直接返回 7 return map[nums[0]]; 8 int max_len = 1; 9 for (int i = 1; i < nums.size(); ++ i) 10 if (nums[i] > nums[0]) { 11 vector<int> t{nums.begin() + i, nums.end()}; 12 max_len = max(max_len, l(map, t) + 1); 13 } 14 map[nums[0]] = max_len; // 記錄以某個數開頭的最長遞增序列長度 15 return max_len; 16 } 17 int lengthOfLIS(vector<int>& nums) { 18 int max_len = 1; 19 unordered_map<int, int> map; // 雜湊表,<開頭的數,最長遞迴序列長度> 20 for (int i = 0; i < nums.size(); ++ i) { 21 vector<int> t(nums.begin() + i, nums.end()); 22 max_len = max(max_len, l(map, t)); 23 } 24 return max_len; 25 } 26 };
解法二:動態規劃
看來暴力是不行滴,還得動態規劃。(時間複雜度O(n^2),AC了)
1 class Solution { 2 public: 3 int lengthOfLIS(vector<int>& nums) { 4 vector<int> dp(nums.size(), 0); // 記錄以nums[i]為結尾的最長遞增子序列長度 5 for (int i = 1; i < nums.size(); ++ i) 6 for (int j = 0; j < i; ++ j) // 找一個最長的遞增序列,接到它後面 7 if (nums[j] < nums[i]) 8 dp[i] = max(dp[i], dp[j] + 1); 9 return *max_element(dp.begin(), dp.end()) + 1; 10 } 11 };
解法三:動態規劃 + 二分查詢
動態規劃方法是可行的,但是O(n^2)的時間複雜度還是較高,使用二分查詢方法可以進一步優化。(時間複雜度O(nlogn),大提升)
1 class Solution { 2 public: 3 int lengthOfLIS(vector<int>& nums) { 4 vector<int> dp(1, *nums.begin()); // 維護一個數組,用來存放最長的遞增子序列 5 int left = 0, right = 0, mid = 0; 6 for (int i = 1; i < nums.size(); ++ i) { // 遍歷一遍nums尋找每個元素在最長子序列中的插入位置 7 if (nums[i] > *(dp.end() - 1)) { // 如果當前元素比序列中所有元素都大,直接插到末尾 8 dp.push_back(nums[i]); 9 continue; 10 } 11 left = -1; // 否則的話,替換掉序列中第一個大於等於它的元素,這樣可以保證得到最長的遞增序列 12 right = dp.size(); 13 while (left + 1 != right) { 14 mid = (left + right) / 2; 15 if (dp[mid] >= nums[i]) 16 right = mid; 17 else 18 left = mid; 19 } 20 dp[right] = nums[i]; 21 } 22 return dp.size(); 23 } 24 };