leetcode 動態規劃(一)

目錄

338. 比特位計數

 面試題47. 禮物的最大價值

1277. 統計全爲 1 的正方形子矩陣

1227. 飛機座位分配概率

300. 最長上升子序列

343. 整數拆分

70. 爬樓梯

746. 使用最小花費爬樓梯

873. 最長的斐波那契子序列的長度

62. 不同路徑


338. 比特位計數

https://leetcode-cn.com/problems/counting-bits/submissions/

給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍中的每個數字 i ,計算其二進制數中的 1 的數目並將它們作爲數組返回。

示例 1:    輸入: 2    輸出: [0,1,1]
示例 2:   輸入: 5    輸出: [0,1,1,2,1,2]
進階:給出時間複雜度爲O(n*sizeof(integer))的解答非常容易。但你可以在線性時間O(n)內用一趟掃描做到嗎?要求算法的空間複雜度爲O(n)。你能進一步完善解法嗎?要求在C++或任何其他語言中不使用任何內置函數(如 C++ 中的 __builtin_popcount)來執行此操作。

思路

方法一:先掃描一遍,將2的整數冪置爲1,2的整數冪之間的數字可由該整數冪及其前面的數字相加得到,則1的位數也是二者之和,例如4-8之間的5可以由4+1得到,1的位數是2。

class Solution(object):
    def countBits(self, num):
        """
        :type num: int
        :rtype: List[int]
        """
        if num == 0:
            return [0]
        res = [-1] * (num + 1)
        res[0] = 0

        i = 1
        # 先掃描一遍,將2的整數冪置爲1
        while i <= num:
            res[i] = 1
            i = 2 * i
        # pre用來表示i前最接近i的一個2的整數冪
        pre, j = 0, 1
        for i in range(1, num + 1):
            if res[i] == 1:
                pre, j = i, 1
            else:
                # 2的整數冪之間的數字可由該整數冪及其前面的數字相加得到
                res[i] = res[pre] + res[j]
                j += 1

        return res

方法一的僅掃描一遍的版本,https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/ 的方法二,動態規劃+最高有效位,例如8-16之間的15,與7只差一個8。我們可以使用 [0,7] 作爲藍本來得到 [8,15]。

class Solution(object):
    def countBits(self, num):
        res = [0] * (num + 1)
        if num == 0:
            return [0]
        i, res[0] = 1, 0
        while i <= num:
            res[i] = 1
            last_idx = min(2 * i, num) 
            for j in range(i + 1, last_idx + 1):
                res[j] = res[j - i] + 1
            i *= 2
        return res

方法二:動態規劃+最低有效位,觀察x 和 x′=x/2 的關係:

x=(1001011101)2 =(605) 10

x ′ =(100101110) 2 =(302) 10
​可以發現 x ′與x 只有一位不同,這是因爲x ′可以看做 x 移除最低有效位的結果。

這樣,我們就有了下面的狀態轉移函數:P(x)=P(x/2)+(xmod2)

class Solution(object):
    def countBits(self, num):
        res = [0] * (num + 1)

        for i in range(0, num + 1):
            res[i] = res[i // 2] + (i % 2)

        return res

 面試題47. 禮物的最大價值

https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/

在一個 m*n 的棋盤的每一格都放有一個禮物,每個禮物都有一定的價值(價值大於 0)。你可以從棋盤的左上角開始拿格子裏的禮物,並每次向右或者向下移動一格、直到到達棋盤的右下角。給定一個棋盤及其上面的禮物的價值,請計算你最多能拿到多少價值的禮物?

示例 1:輸入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
輸出: 12,解釋: 路徑 1→3→5→2→1 可以拿到最多價值的禮物
提示:0 < grid.length <= 200。0 < grid[0].length <= 200

思路

一:動態規劃二維DP,轉移方程res[i][j] = max(res[i - 1][j], res[i][j - 1]) + grid[i][j],其上邊的和左邊的取最大加上該值即可。

class Solution(object):
    def maxValue(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        if not grid or not grid[0]:
            return 0

        m, n = len(grid), len(grid[0])
        res = [[0] * n for _ in range(m)]
        res[0][0] = grid[0][0]
        for i in range(1, m):
            res[i][0] = res[i - 1][0] + grid[i][0]
        for j in range(1, n):
            res[0][j] = res[0][j - 1] + grid[0][j]    

        for i in range(1, m):
            for j in range(1, n):
                res[i][j] = max(res[i - 1][j], res[i][j - 1]) + grid[i][j]
            
        return res[m - 1][n - 1]

二:對一進行空間上的優化。res[i][j] = max(res[i - 1][j], res[i][j - 1]) + grid[i][j],res[i - 1][j]是上一行的值,當只有一行時,則是未更新的時候的值,即此處的res[j],res[i][j - 1]是該行已更新的左側的值,當只有一行時,則亦是已更新的左側的值,即此處的res[j-1]。

class Solution(object):
    def maxValue(self, grid):
        if not grid or not grid[0]:
            return 0

        m, n = len(grid), len(grid[0])
        res = [0] * n 

        for i in range(0, m):
            res[0] += grid[i][0]
            for j in range(1, n):
                res[j] = max(res[j], res[j - 1]) + grid[i][j]
            
        return res[n - 1]

1277. 統計全爲 1 的正方形子矩陣

https://leetcode-cn.com/problems/count-square-submatrices-with-all-ones/

給你一個 m * n 的矩陣,矩陣中的元素不是 0 就是 1,請你統計並返回其中完全由 1 組成的 正方形 子矩陣的個數。

示例 1:輸入:matrix =[[0,1,1,1], [1,1,1,1], [0,1,1,1]]
輸出:15,解釋: 邊長爲 1 的正方形有 10 個。邊長爲 2 的正方形有 4 個。邊長爲 3 的正方形有 1 個。正方形的總數 = 10 + 4 + 1 = 15.
示例 2:輸入:matrix = [[1,0,1],[1,1,0],[1,1,0]]
輸出:7,解釋:邊長爲 1 的正方形有 6 個。 邊長爲 2 的正方形有 1 個。正方形的總數 = 6 + 1 = 7。

提示:1 <= arr.length <= 300,1 <= arr[0].length <= 300,0 <= arr[i][j] <= 1

思路

一:暴力法,對每一個下標(i,j)的元素若爲1,則開始統計以該下標爲右下角的正方形的個數(判斷是正方形,對每一個可能的邊長,看其區域中是否均爲1,若是,則標爲正方形,個數加一;否則退出循環,判斷下一個下標)。

class Solution(object):
    def countSquares(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: int
        """
        m, n = len(matrix), len(matrix[0])
        res, cnt = [[0] * n for _ in range(m)], 0

        for i in range(m):
            for j in range(n):
                if matrix[i][j] != 1:
                    continue
                length = min(i, j) + 1
                for l in range(0, length):
                    flag = True
                    for x in range(i - l, i + 1):
                        if not flag:
                            break
                        for y in range(j - l, j + 1):
                            if matrix[x][y] != 1:
                                flag = False
                                break
                    if flag:
                        res[i][j] += 1
                    else:
                        break
                cnt += res[i][j] 
        return cnt

二:對一進行改進,改進判斷正方形的方法(先用sum_rec對matrix求和,其中sum_rec[i][j],表示一i,j爲下標的左上角的和,只要該區域的面積與其中1的個數相同,則是一個正方形)。

class Solution(object):
    def countSquares(self, matrix):
        m, n = len(matrix), len(matrix[0])
        sum_rec, res, cnt = [[0] * n for _ in range(m)], [[0] * n for _ in range(m)], 0
        sum_rec[0][0] = matrix[0][0]

        for i in range(1, m):
            sum_rec[i][0] = sum_rec[i - 1][0] + matrix[i][0]
        for j in range(1, n):
            sum_rec[0][j] = sum_rec[0][j - 1] + matrix[0][j]

        for i in range(1, m):
            for j in range(1, n):
                sum_rec[i][j] = sum_rec[i - 1][j] + sum_rec[i][j - 1] - sum_rec[i- 1][j - 1] + matrix[i][j]

        for i in range(m):
            for j in range(n):
                if matrix[i][j] != 1:
                    continue
                # res[i][j] = 1
                length = min(i + 1, j + 1) 
                for l in range(0, length):
                    x, y = i - l, j - l
                    area = (l + 1) ** 2
                    num_1 = sum_rec[i][j]
                    if x - 1 >= 0:
                        num_1 -= sum_rec[x - 1][j] 
                    if y - 1 >= 0:
                        num_1 -= sum_rec[i][y - 1]
                        if x - 1 >= 0:
                            num_1 +=  sum_rec[x - 1][y - 1]
                    if area == num_1:
                        res[i][j] += 1
                    else:
                        break
                cnt += res[i][j]
        return cnt       

三:動態規劃,轉自https://leetcode-cn.com/problems/count-square-submatrices-with-all-ones/solution/tong-ji-quan-wei-1-de-zheng-fang-xing-zi-ju-zhen-f/,首先,暴力解就是以矩陣每一個點爲起點,依次判斷邊長爲1,2,3,...,min(矩陣長, 矩陣寬)的區域是否是正方形,顯然複雜度是過不了。很容易知道,上述過程在判斷較大區域是否爲正方形的時候,並沒有用到前面計算的結果,每一次判斷都從頭開始。這也是複雜度過高的原因。

那麼怎麼利用之前判斷過的結果呢?舉個例子,比如我要判斷以(2, 3)爲右下角邊長爲3的正方形區域(紅色邊框區域)是否是全爲1:

image.png

先判斷(i, j)位置是否爲1,如果否,則顯然不滿足;如果是,進行下一步判斷
判斷分別以(i - 1, j), (i - 1, j - 1), (i, j - 1)爲右下角的區域是否能構成邊長爲2的正方形,如果能,那就滿足條件。

基於上述,我們可以看出思路大致跟最大正方形那題差不多,設dp[i][j][k]表示以(i, j)爲右下角,邊長爲k的正方形區域是否全爲1,那麼易得出如下狀態轉移方程:dp[i][j][k] = (matrix[i][j] == 1 \&\& dp[i - 1][j][k - 1] \&\& dp[i][j - 1][k - 1] \&\& dp[i - 1][j - 1] [k - 1])

class Solution(object):
    def countSquares(self, matrix):
        m, n = len(matrix), len(matrix[0])
        
        rec, res = [[0] * n for _ in range(m)], 0
        
        for i in range(m):
            rec[i][0] = matrix[i][0]
            res += matrix[i][0]
        
        for j in range(1, n):
            rec[0][j] = matrix[0][j]
            res += matrix[0][j]
        
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] != 1:
                    continue 
                rec[i][j] = min([rec[i][j - 1], rec[i - 1][j], rec[i - 1][j - 1]]) + 1
                res += rec[i][j]
        return res 

1227. 飛機座位分配概率

https://leetcode-cn.com/problems/airplane-seat-assignment-probability/

有 n 位乘客即將登機,飛機正好有 n 個座位。第一位乘客的票丟了,他隨便選了一個座位坐下。剩下的乘客將會:如果他們自己的座位還空着,就坐到自己的座位上,當他們自己的座位被佔用時,隨機選擇其他座位第 n 位乘客坐在自己的座位上的概率是多少?

示例 1:輸入:n = 1,輸出:1.00000,解釋:第一個人只會坐在自己的位置上。
示例 2:輸入: n = 2,輸出: 0.50000,解釋:在第一個人選好座位坐下後,第二個人坐在自己的座位上的概率是 0.5。

提示:1 <= n <= 10^5

思路

一:參考各路題解,該題定位是數學+腦筋急轉彎+動態規劃。

先分析第一個乘客的可能選擇

  1. 選擇正確的位置(即自己的位置),其概率是1/n,則後面所有乘客均會坐在正確的位置,在第一個選擇正確的條件下,後面所有的均會正確(即此條件下,概率爲1),自然第 n 位乘客必定坐在自己的座位上。(1/n * 1.0)
  2. 選擇坐在第n個乘客的位置上,其概率是1/n,則無論如何第n個乘客均坐不到自己座位(即此條件下,概率爲0.0),(1/n * 0.0)
  3. 其在剩餘的n-2個座位中的一個,設其坐在第K個乘客的位置上,那麼會造成第K個乘客不能坐在自己的位置上,但是第2至K-1個乘客均是坐在自己的位置上。此時相當於求解一個子問題,而且前K-1個人的選擇不會對後續結果產生影響,這個字問題就是,將第K個乘客當作新的第一個乘客,能這麼做的原因是因爲他和第一位選擇情況完全一樣,不過他的正確的座位(可以理解爲不影響別人的座位)是第一個乘客的座位,翻譯一下原題,“有 n-K+1 位乘客即將登機,飛機正好有 n -K+1個座位。第一(原來的第K)位乘客的票丟了,他隨便選了一個座位坐下。剩下的乘客將會:如果他們自己的座位還空着,就坐到自己的座位上,當他們自己的座位被佔用時,隨機選擇其他座位最後一位乘客坐在自己的座位上的概率是多少?”

用列表dp來存放概率,其中dp[i]-表示n=i,即有i名乘客,最後一名乘客座位正確的概率。

dp[1] = 1.0

dp[2] = 1/2 * 1 + 1/2 * 0 = 0.5

dp[3] = 1/3 * 1 + 1/3 * 0 + 1/3 * dp[2] = 0.5

dp[4] = 1/4 * 1 + 1/4 * 0 + 1/4 * dp[3] + 1/4 * dp[2]= 0.5

(1/4 * 1,第一個乘客坐正確的情況;1/4 * 0,第一個乘客坐在第四個乘客的位置,則不可能正確;1/4 * dp[3],第一個乘客坐在第二個乘客的位置,則字問題規模爲3,故有dp[3]; 1/4 * dp[3],第一個乘客坐在第三個乘客的位置,則字問題規模爲2,故有dp[2])

遞推下去我們發現當n>=2時,概率一直爲0.5。

class Solution(object):
    def nthPersonGetsNthSeat(self, n):
        """
        :type n: int
        :rtype: float
        """
        if n == 1:
            return 1.0
        return 0.5

300. 最長上升子序列

https://leetcode-cn.com/problems/longest-increasing-subsequence/

給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:輸入: [10,9,2,5,3,7,101,18],輸出: 4 ,解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。
說明:可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。你算法的時間複雜度應該爲 O(n2) 。
進階: 你能將算法的時間複雜度降低到 O(n log n) 嗎?

思路

一:動態規劃,dp[i]-表示以下標爲i的元素結尾的最長上升子序列的長度,每次都要遍歷i之前的元素,看看是否能接在其後面。

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0

        dp = [1] * len(nums)

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

二:貪心+二分查找,轉自官方題解https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode-soluti/,考慮一個簡單的貪心,如果我們要使上升子序列儘可能的長,則我們需要讓序列上升得儘可能慢,因此我們希望每次在上升子序列最後加上的那個數儘可能的小。

基於上面的貪心思路,我們維護一個數組 res[i] ,表示長度爲 i 的最長上升子序列的末尾元素的最小值,用 len 記錄目前最長上升子序列的長度,起始時 len 爲 1,res[0]=nums[0]。同時我們可以注意到 res[i] 是關於 i 單調遞增的。因爲如果 res[j]≥res[i] 且 j<i,我們考慮從長度爲 i 的最長上升子序列的末尾刪除 i−j 個元素,那麼這個序列長度變爲 j ,且第 j 個元素 x(末尾元素)必然小於 res[i],也就小於 res[j]。那麼我們就找到了一個長度爲 j 的最長上升子序列,並且末尾元素比 res[j] 小,從而產生了矛盾。因此數組 d[] 的單調性得證。

我們依次遍歷數組 nums[] 中的每個元素,並更新數組res[] 和 len 的值。如果 nums[i]>res[len] 則更新 len=len+1,否則在 res[1…len]中找滿足 res[i−1]<nums[j]<res[i] 的下標 i,並更新 res[i]=nums[j]。根據 d數組的單調性,我們可以使用二分查找尋找下標 i,優化時間複雜度。

最後整個算法流程爲:

設當前已求出的最長上升子序列的長度爲 len(初始時爲 1),從前往後遍歷數組 nums,在遍歷到 nums[i] 時:

如果 nums[i]>res[len] ,則直接加入到 res 數組末尾,並更新 len=len+1;

否則,在 res 數組中二分查找,找到第一個比 nums[i] 小的數 res[k] ,並更新 res[k+1]=nums[i]。

以輸入序列 [0,8,4,12,2] 爲例:

第一步插入 0,res=[0];

第二步插入 8,res=[0,8];

第三步插入 4,res=[0,4];

第四步插入 12,res=[0,4,12];

第五步插入 2,res=[0,2,12]。

最終得到最大遞增子序列長度爲 3。

class Solution(object):
    def lengthOfLIS(self, nums):
        if not nums:
            return 0

        res, cnt = [nums[0]], 0
        for i in range(1, len(nums)):
            if nums[i] > res[-1]:
                res.append(nums[i])
            else:
                # [l,r]
                l, r, target = 0, len(res) -1, nums[i]
                loc = -1
                # 找到第一個比nums[i]大的替換掉
                while l <= r:
                    mid = l + (r - l) // 2
                    if target <= res[mid]:
                        loc = mid
                        r = mid - 1
                    else:
                        l = mid + 1
                res[loc] = target             
        return len(res)

343. 整數拆分

https://leetcode-cn.com/problems/integer-break/

給定一個正整數 n,將其拆分爲至少兩個正整數的和,並使這些整數的乘積最大化。 返回你可以獲得的最大乘積。

示例 1:輸入: 2,輸出: 1,解釋: 2 = 1 + 1, 1 × 1 = 1。
示例 2:輸入: 10,輸出: 36,解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
說明: 你可以假設 n 不小於 2 且不大於 58。

題解

一:用res記錄的是截止目前整數i,將其拆分爲至少兩個正整數的和,這些整數的乘積的最大值。其中注意到取最大值時比較了res[j]*(i - j)j * (i - j),這是因爲res[j]中存儲的是拆分成至少兩個正整數的最大乘積,但是存在最大值是該數本身,這種情況在計算其本身的時候不合法,因爲沒有拆分,但是在計算後續的時候是合法的(由於內層只看到了i的前一個數,相當於保證至少拆分成兩個整數)。

class Solution(object):
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """
        res = [1] * (n + 1)
        for i in range(3, n + 1):
            for j in range(1, i):
                res[i] = max([res[i], res[j] * (i - j), j * (i - j)])
        return res[-1]

70. 爬樓梯

https://leetcode-cn.com/problems/climbing-stairs/

假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:輸入: 2,輸出: 2,解釋: 有兩種方法可以爬到樓頂。
1.  1 階 + 1 階
2.  2 階
示例 2:輸入: 3,輸出: 3,解釋: 有三種方法可以爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階

題解

一:用res記錄到達當前位置的方法數,我們發現到達當前位置只有兩種途徑,要麼從前面一個臺階跨一個臺階到達;要麼從前面兩個臺階跨兩個臺階到達,故遞推公式res[i] = res[i - 1] + res[i - 2]

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n <= 1:
            return 1
        if n == 2:
            return 2
        res = [0] * (n + 1)
        res[1], res[2] = 1, 2

        for i in range(3, n + 1):
            res[i] = res[i - 1] + res[i - 2]
        return res[-1]

二:對空間進行優化,我們發現只關係到前面兩個值,用變量記錄下來,並在過程中更新。

class Solution(object):
    def climbStairs(self, n):
        if n <= 1:
            return 1
        if n == 2:
            return 2
        res_2, res_1 = 1, 2

        for i in range(3, n + 1):
            res = res_1 + res_2
            res_2 = res_1
            res_1 = res
        return res

746. 使用最小花費爬樓梯

https://leetcode-cn.com/problems/min-cost-climbing-stairs/

數組的每個索引做爲一個階梯,第 i個階梯對應着一個非負數的體力花費值 cost[i](索引從0開始)。每當你爬上一個階梯你都要花費對應的體力花費值,然後你可以選擇繼續爬一個階梯或者爬兩個階梯。您需要找到達到樓層頂部的最低花費。在開始時,你可以選擇從索引爲 0 或 1 的元素作爲初始階梯。

示例 1:輸入: cost = [10, 15, 20],輸出: 15,解釋: 最低花費是從cost[1]開始,然後走兩步即可到階梯頂,一共花費15。
示例 2:輸入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1],輸出: 6,解釋: 最低花費方式是從cost[0]開始,逐個經過那些1,跳過cost[3],一共花費6。
注意:cost 的長度將會在 [2, 1000]。每一個 cost[i] 將會是一個Integer類型,範圍爲 [0, 999]。

題解

一:dp[i]表示到達i臺階(必須踩在i臺階上)的最小花費,至於最後的return\ min(dp[-1], dp[-2])是因爲示例1,只要到達頂層或者到達頂層前面的一層均屬於已通過。

class Solution(object):
    def minCostClimbingStairs(self, cost):
        """
        :type cost: List[int]
        :rtype: int
        """
        n = len(cost)
        dp = [0] * n
        dp[0], dp[1] = cost[0], cost[1]

        for i in range(2, n):
            dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]
        return min(dp[-1], dp[-2])

873. 最長的斐波那契子序列的長度

https://leetcode-cn.com/problems/length-of-longest-fibonacci-subsequence/

如果序列 X_1, X_2, ..., X_n 滿足下列條件,就說它是 斐波那契式 的:n >= 3,對於所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}
給定一個嚴格遞增的正整數數組形成序列,找到 A 中最長的斐波那契式的子序列的長度。如果一個不存在,返回  0 。

(回想一下,子序列是從原序列 A 中派生出來的,它從 A 中刪掉任意數量的元素(也可以不刪),而不改變其餘元素的順序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一個子序列)

示例 1:輸入: [1,2,3,4,5,6,7,8],輸出: 5
解釋:最長的斐波那契式子序列爲:[1,2,3,5,8] 。
示例 2:輸入: [1,3,7,11,12,14,18],輸出: 3
解釋:最長的斐波那契式子序列有:[1,11,12],[3,11,14] 以及 [7,11,18] 。
提示:3 <= A.length <= 1000,1 <= A[0] < A[1] < ... < A[A.length - 1] <= 10^9

題解

一:先確定下斐波那契數列的前兩個值,那麼整個斐波那契各個位置上的值均可確定,在後面的值中看是否有數列中下一個值,若有繼續遍歷,直到沒有了記錄下這組長度,尋找可以用二分法,時間複雜度O(n^2 lg(n))

class Solution(object):
    def lenLongestFibSubseq(self, A):
        """
        :type A: List[int]
        :rtype: int
        """
        res = 0
        for i in range(len(A)):
            for j in range(i + 1, len(A)):
                a_1, a_2, locla_res = A[j], A[i], 2
                while True:
                    a = a_1 + a_2
                    l, r, flag = j + 1, len(A), False
                    # [l, r), 二分查找
                    while l < r:
                        mid = l + (r - l) // 2
                        if A[mid] == a:
                            flag = True
                            break
                        elif A[mid] < a:
                            l = mid + 1
                        else:
                            r = mid
                    if flag:
                        locla_res += 1
                        a_2 = a_1
                        a_1 = a 
                    else:
                        break
                res = max(res, locla_res)
        
        return res if res >= 3 else 0

二:將二分查找改用set集合來查找下一個數,時間複雜度O(n^2lgn),由於藉助了數據結構,所以工程實現上會快一點。

class Solution(object):
    def lenLongestFibSubseq(self, A):
        res, rec = 0, set(A)
        for i in range(len(A)):
            for j in range(i + 1, len(A)):
                a_1, a_2, locla_res = A[j], A[i], 2
                while True:
                    a = a_1 + a_2
                    l, r = j + 1, len(A)
                    if a not in rec:
                        break
                    else:
                        locla_res += 1
                        a_2 = a_1
                        a_1 = a 
                res = max(res, locla_res)
        if res < 3:
            return 0
        return res

三:這種實現上更快,前面兩種方法不需要判斷,通過前面的找後面的,必定滿足條件。而該方法,是看該數能不能添加到某一個數列的後面,我們必須保證小的下標在前面,因爲是單向的,否則只會等於2。參考了官方題解https://leetcode-cn.com/problems/length-of-longest-fibonacci-subsequence/solution/zui-chang-de-fei-bo-na-qi-zi-xu-lie-de-chang-du-by/

from collections import defaultdict
class Solution(object):
    def lenLongestFibSubseq(self, A):
        logest = defaultdict(lambda:2)
        index = {val: k for k, val in enumerate(A)}
        res = 0
        for i in range(len(A)):
            for j in xrange(i):
                idx = index.get(A[i] - A[j], None)
                # 因爲idx可能爲0,所以不能用idx,因爲這樣idx=0也過不了if判斷
                if idx is not None and idx < j:
                    local_res = logest[j, i] = logest[idx, j] + 1
                    res = max(local_res, res)
        return res if res >= 3 else 0

62. 不同路徑

https://leetcode-cn.com/problems/unique-paths/

一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲“Start” )。機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲“Finish”)。問總共有多少條不同的路徑?

例如,上圖是一個7 x 3 的網格。有多少可能的路徑?

示例 1:輸入: m = 3, n = 2,輸出: 3
解釋:從左上角開始,總共有 3 條路徑可以到達右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:輸入: m = 7, n = 3,輸出: 28
提示:1 <= m, n <= 100,題目數據保證答案小於等於 2 * 10 ^ 9

題解

一:用dp[i][j]記錄到達位置(i,j)的方法數,到達(i,j)只有兩種途徑,從其左方向右移動一格,或從其上方向下移動一格。故有遞推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],其中dp = [[1] * n for _ in xrange(m)],相當於做了初始化,最左側的列最上面的行均只有一種方法。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        dp = [[1] * n for _ in xrange(m)]

        for i in xrange(1, m):
            for j in xrange(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]

二:優化空間

class Solution(object):
    def uniquePaths(self, m, n):
        dp = [1] * n

        for i in xrange(1, m):
            for j in xrange(1, n):
                dp[j] = dp[j] + dp[j - 1]
        return dp[n - 1]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章