LeetCode初級算法的Python實現--動態規划


動態規划的本質是遞歸;所以做題之前一定要會遞歸;遞歸式就是狀態轉移方程;這里將會介紹使用動態規划做題的思維方式。

統一的做題步驟:

1、用遞歸的方式寫出代碼;(此方法寫的代碼在leetcode中一定會超時)   
2、找冗余,去冗余;  
3、找邊界;

1、爬樓梯

假設你正在爬樓梯。需要 n 步你才能到達樓頂。
每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1:

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1.1 步 + 1 步
2.2 步   

寫遞歸

通常情況下,我們會將問題從大到小處理,也就是這里如果有n個台階,先處理第n個台階,然后處理n-1,n-2;所以在這里,總體思路是:第n個台階可以走1步,也可以走兩步,當走第一步時,n-1個台階又可以分為n-1和n-2,不斷遞歸,所以可寫為recursionClimbStairs(n - 1),當n台階走2步時,recursionClimbStairs(n - 2),然后將兩種情況相加;示意圖如下圖所示:

遞歸代碼如下:

def recursionClimbStairs(self, n):
    if n <= 2:
        return n
    return self.recursionClimbStairs(n - 1) + self.recursionClimbStairs(n - 2)

去冗余:

從上圖可知,左下角的n-2和右邊的n-2重復了,都進行了計算,所以嚴重拉長的時間;所以,我們可以把計算了的存起來;首先定義數組nums,當n為1時,方法只有一種,當n為2時,方法有兩種,所以nums的前兩個值分別為1、2;然后從2到n開始循環,當前值為把上面的遞歸抄下來即可,就是把方法名改成nums,因為存起來了;所以,數組最后的那個值就是最后的答案;如果不理解,仔細把下面幾道題按照這種思慮去寫,多寫幾次就理解了。

找邊界

因為循環中有n-2,所以n<=2的情況都需要在循環前寫好;

def climbStairs(self, n):
    nums = [1, 2]
    if n <= 2:
        return n
    for i in range(2, n):
        nums.append(nums[i - 1] + nums[i - 2])
    return nums[len(nums) - 1]

2、買賣股票的最佳時機

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多只允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。
注意你不能在買入股票前賣出股票。
示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格。   

寫遞歸

從最后第n個數開始,第n個數減去前n-1個數的最小值為當前遞歸層的最大利益,然后將每層的最大利益再去較大值;代碼如下

遞歸代碼如下:

def recursionMaxProfix(self, prices):
    if len(prices) < 2:
        return 0
    return max(prices[- 1] - min(prices[:- 1]),
               self.recursionMaxProfix(prices[:- 1]))

去冗余:

前1個數利益為0,這里result用來存結果,轉換方法和上面那個例子一樣,但是其實如果直接使用上面的遞歸轉換的代碼為max(prices[i] - min(prices[:i]), result[i - 1]),但是由於min(prices[:i])又是一層循環所以會導致時間又超時,所以這里可以優化為從第一個數開始就獲取最小的值存起來,如下代碼所示;

找邊界

當列表的數值小於2個時,結果只可能為0;

def maxProfit(self, prices):
    """
    :type prices: List[int]
    :rtype: int
    """
    result = [0]
    if len(prices) < 2:
        return 0
    minPrice = prices[0]
    for i in range(1, len(prices)):
        minPrice = min(minPrice, prices[i - 1])
        result.append(max(prices[i] - minPrice, result[i - 1]))
    return result[-1]

3、最大子序和

給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例 1:

輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。

寫遞歸

思想:從最后第n-1個數開始,所以列表從右到左開始加,反之也可以,但里面的代碼要改,思想不變;

遞歸代碼如下:

def recursionMaxSubArray(self, idx, nums, maxSum):
    if idx < 0:
        return maxSum
    nums[idx] = max(nums[idx], nums[idx + 1] + nums[idx])
    maxSum = max(nums[idx], maxSum)
    return self.recursionMaxSubArray(idx - 1, nums, maxSum)

去冗余:

遞歸是從第n個數開始,循環可以從左到右,遞歸中使用了maxSum變量存儲最大值,而在這里循環中可直接使用數組,和上面的例子相似,但是由於這里的nums變量循環的時候沒有用到前面的值,所以直接存入nums中,代碼如下所示;

找邊界

因為循環中有i-1,且第0個數也不需要計算,所以直接從1開始循環即可;

def maxSubArray(self, nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    for i in range(1, len(nums)):
        nums[i] = max(nums[i] + nums[i - 1], nums[i])
    return max(nums)

4、打家劫舍

你是一個專業的小偷,計划偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:

輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,然后偷竊 3 號房屋 (金額 = 3)。
 偷竊到的最高金額 = 1 + 3 = 4 。  

寫遞歸

因為必須相隔一個數,且每個數都是非負的,所以情況分為兩種,一種從第一個數開始,一種從第二數開始;兩種情況分別遞歸取較大值;從最后第n個數開始,第n個數加上n-2位置的遞歸和n-1的遞歸相比取較大值;代碼如下

遞歸代碼如下:

def recursionRob(self, idx, nums):
    if idx < 0:
        return 0
    return max(nums[idx] + self.recursionRob(idx - 2, nums), self.recursionRob(idx - 1, nums))

去冗余:

直接將上述的方法名改成數值即可,如下代碼所示;

找邊界

因為循環中有i-2,所以列表長度為2之前的需要處理;

def rob(self, nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    if len(nums) == 0:
        return 0
    if len(nums) == 1:
        return nums[0]
    nums[1] = max(nums[0], nums[1])
    for i in range(2, len(nums)):
        nums[i] = max(nums[i] + nums[i - 2], nums[i - 1])
    return nums[len(nums) - 1]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM