1. 程式人生 > 其它 >【LeetCode】300.最長遞增子序列——暴力遞迴(O(n^3)),動態規劃(O(n^2)),動態規劃+二分法(O(nlogn))

【LeetCode】300.最長遞增子序列——暴力遞迴(O(n^3)),動態規劃(O(n^2)),動態規劃+二分法(O(nlogn))

  演算法新手,刷力扣遇到這題,搞了半天終於搞懂了,來這記錄一下,歡迎大家交流指點。

題目描述:

給你一個整數陣列 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 };