1. 程式人生 > 其它 >300. 最長遞增子序列

300. 最長遞增子序列

1. 題目

  給你一個整數陣列 nums ,找到其中最長嚴格遞增子序列的長度。

  子序列是由陣列派生而來的序列,刪除(或不刪除)陣列中的元素而不改變其餘元素的順序。例如,[3,6,2,7] 是陣列 [0,3,1,6,2,2,7] 的子序列。

2. 示例

示例1:

輸入:nums = [10,9,2,5,3,7,101,18]
輸出:4
解釋:最長遞增子序列是 [2,3,7,101],因此長度為 4 。

示例2:

輸入:nums = [0,1,0,3,2,3]
輸出:4

示例3:

輸入:nums = [7,7,7,7,7,7,7]
輸出:1

3. 題解

  2種解題思路:動態規劃、二分搜尋

3.1 動態規劃

  動態規劃的核心思想是數學歸納法

  設計動態規劃,首先需要一個dp陣列,假設dp[0....i - 1]都已經算出來了,然後問自己,怎麼通過這些結果算出dp[i]?

  dp[i]表示以nums[i]這個數結尾的最長遞增子序列的長度。那麼以nums[i]結尾的最長遞增子序列起碼要包含它自己,即要找到前面序列結尾比nums[i]小的子序列,然後把nums[i]拼接上去,就形成了以nums[i]結尾的新的子序列,並在前面子序列的基礎上加1。

for j in range(0, i):
    if nums[i] > nums[j]:
        dp[i] = max(dp[i], dp[j] + 1)

  最長子序列,只需要對dp陣列遍歷一次,取最大值即可。

for i in range(n):
    res = max(res, dp[i])

3.2 二分搜尋

  二分搜尋相對來說,難度大一些。以陣列[10,9,2,5,3,7,101,18]為例,對其分堆。

  首先10,對於第一個元素,單獨為堆,此時堆數res = 1;

  元素9,從0堆開始找,9小於10,放入0堆,res=1;

  元素2,從0堆開始找,2小於9,放入0堆,res=1;

  元素5,從0堆開始找,5大於2,此時只有堆0,那麼需要再次建立一個堆來存放,res+=1,res=2;

  元素3,從0堆開始找,3大於2,再找第1堆,3小於5,放入,res=2;

  元素7,7大於2,7大於3,建立新堆,第2堆堆頂元素為7,res=3;

  元素101,大於0,1,2堆頂元素,建立新堆,res=4;

  元素18,大於0,1,2堆頂元素,放入第3堆,res=4;

  輸出結果:4(堆的個數就是最長子序列的長度,因為在後面堆裡面一定能找到小於等於前面元素)。

4. Code實現

4.1 動態規劃

 1 class Solution:
 2     # 動態規劃
 3     def lengthOfLIS(self, nums: List[int]) -> int:
 4         if len(nums) <= 1:
 5             return len(nums)
 6         n = len(nums)
 7         dp, res = [1 for _ in range(n)], 0
 8         for i in range(n):
 9             for j in range(0, i):
10                 if nums[i] > nums[j]:
11                     dp[i] = max(dp[i], dp[j] + 1)
12             res = max(res, dp[i])
13         return res

4.2 二分搜尋

 1 class Solution:
 2 # 二分搜尋
 3     def lengthOfLISB(self, nums: List[int]) -> int:
 4         n = len(nums)
 5         if n < 2:
 6             return n
 7         top = [0 for _ in range(n)]
 8         # 堆數
 9         res = 0
10         for i in range(n):
11             # 要處理的牌
12             cur = nums[i]
13             # 搜尋左邊界的二分搜尋
14             left, right = 0, res
15             while left < right:
16                 mid = (left + right) // 2
17                 if top[mid] > cur:
18                     right = mid
19                 elif top[mid] < cur:
20                     left = mid + 1
21                 else:
22                     right = mid
23             # 沒找到合適的堆,新建一堆
24             if left == res:
25                 res += 1
26             top[left] = cur
27         return res

5. 結語

  努力去愛周圍的每一個人,付出,不一定有收穫,但是不付出就一定沒有收穫! 給街頭賣藝的人零錢,不和深夜還在擺攤的小販討價還價。願我的部落格對你有所幫助(*^▽^*)(*^▽^*)!

  如果客官喜歡小生的園子,記得關注小生喲,小生會持續更新(#^.^#)(#^.^#)。

但行好事 莫問前程