1. 程式人生 > >Leetcode:300.最長上升子序列

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]出現時,可能有以下三種情況:

  1. nums[n+1]是data中的一個數,那麼nums[n+1]不會影響原有的升序列。
  2. 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)才是升序。綜上,應該替換,但是由此可知,這這麼一來,想要求解升序列是什麼就難
    了。
  3. 剩下只可能是nums[n+1]大於data中的最後一個數,在末尾追加即可。
  4. 以上的查詢,都用二分查詢,特殊之處在於返回1,2,3三種情況。

這樣一來時間複雜度就成了O(n)-O(nlogn),空間複雜度為O(n)。

 

普通解法:

C++程式碼
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;
    }
};

 

參考的高階解法:

C++程式碼
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;
    }
};