日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法 - 动态规划

發布時間:2023/12/4 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法 - 动态规划 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

動態規劃是一種自底向上的算法,通常用于解決最大、最小等最值問題

能使用動態規劃解決的問題,一定具備:

  • 重疊子問題:和暴力搜索不同,需要記錄子問題的解,避免重復求解(剪枝)
  • 最優子結構:子問題達到最值,整體才能達到最值,即以小見大
  • 狀態轉移方程:在每個“狀態”做出的“選擇”會到達什么“狀態”

然后就以合適的順序填表,窮舉所有情況并求最值即可
整體流程:明確 base case -> 明確「狀態」-> 明確「選擇」 -> 定義 dp 數組/函數的含義

文章目錄

  • 1.湊零錢
  • 2.最長遞增子序列*
    • 3.最長回文子序列*
    • 最長回文子串
    • 最長公共子序列
    • 最長公共子串
  • 4.打家劫舍
  • 5.打家劫舍II
  • 6.打家劫舍III *
  • 7.含冷凍期的股票買賣
    • 股票買賣的最佳時機II *
    • 股票買賣的最佳時機III
  • 8.編輯距離
    • 帶權編輯距離
  • 8.01背包問題
  • 9.分割等和子集(背包問題模板)
  • 10.湊硬幣II(完全背包問題)
  • 11.目標和(01背包)
  • 12 n個骰子的點數

1.湊零錢

給你一個整數數組 coins ,表示不同面額的硬幣;以及一個整數 amount ,表示總金額。
計算并返回可以湊成總金額所需的 最少的硬幣個數 。如果沒有任何一種硬幣組合能組成總金額,返回 -1 。

class Solution(object):def coinChange(self, coins, amount):""":type coins: List[int]:type amount: int:rtype: int"""# F[n]為湊n所需要的最少硬幣數# F[n] = min(F[n-k]) + 1,其中k是每個面值dp = [10001 for _ in range(amount + 1)]dp[0] = 0for c in coins:if c <= amount:dp[c] = 1# 填表for i in range(1, amount + 1):for c in coins:if i - c >= 0:dp[i] = min(dp[i], dp[i - c] + 1)return dp[-1] if dp[-1] != 10001 else -1

2.最長遞增子序列*

  • 明確狀態:dp[n] = 以下標n結束的(而非從下標0到下標n的,這種弱綁定),最長序列長度
  • 不是所有的題目都采用“強綁定”,只能說隨機應變
class Solution(object):def lengthOfLIS(self, nums):""":type nums: List[int]:rtype: int"""# F[n] = max(F[k] + 1) if nums[k] < nums[n]n = len(nums)dp = [1 for _ in range(n)]res = 1for i in range(n):for j in range(i):dp[i] = max(dp[j] + 1, dp[i]) if nums[i] > nums[j] else dp[i]res = max(res, dp[i])return res

3.最長回文子序列*

  • dp[ i ][ j ] = 從下標 i 到下標 j 的最長回文子序列的長度(不包括頭尾,即 s[ i ] != s[ j ] 時 dp[ i ][ j ] 可以不為 0)
  • 每次填一個左斜的列
class Solution(object):def longestPalindromeSubseq(self, s):""":type s: str:rtype: int"""# dp[i][j] = 下標i到j中,最長回文子序列的長度length = len(s)dp = [[1 for _ in range(length)] for _ in range(length)]# 初始化兩對角線for i in range(length - 1):if s[i] == s[i + 1]:dp[i][i + 1] = 2# 填表for k in range(2, length):i, j = 0, kwhile (i < length and j < length):if (s[i] == s[j]):dp[i][j] = dp[i + 1][j - 1] + 2else:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])i += 1j += 1return dp[0][length - 1]

最長回文子串

  • 填表的時候,每個位置需要查看其左下方向的值,所以填表順序可以是,先將對角線上的兩列初始化,再按列填表
  • dp[ i ][ j ] = s[ i : j + 1] 是否是回文子串
class Solution(object):def longestPalindrome(self, s):""":type s: str:rtype: str"""if s == "":return s# 動態規劃length = len(s)table = [[False for i in range(length)] for j in range(length)] # n*n的矩陣:指示從i到j是否回文start, end = 0, 0# 初始化對角線上的兩列for i in range(length):table[i][i] = Truefor i in range(length-1):if s[i] == s[i+1]:table[i][i+1] = Truestart, end = i, i + 1# 填表:按列的順序for j in range(2, length):for i in range(0, j-1):flag = table[i+1][j-1]if flag and s[i] == s[j]:table[i][j] = Trueif j - i > end - start:start, end = i, jreturn s[start:end+1]

最長公共子序列

class Solution(object):def longestCommonSubsequence(self, text1, text2):""":type text1: str:type text2: str:rtype: int"""# dp[i][j] = t1的前i個字母,和t2的前j個字母的最長相同子序列長度l1, l2 = len(text1), len(text2)dp = [[0 for _ in range(l2 + 1)] for _ in range(l1 + 1)]# 填表,按行填for i in range(1, l1 + 1):for j in range(1, l2 + 1):if text1[i - 1] == text2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1else:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])return dp[-1][-1]

最長公共子串

class Solution:def LCS(self , str1 , str2 ):# write code herem, n = len(str1), len(str2)if m == 1:return str1 elif n == 1:return str2 dp = [[0 for _ in range(n)] for _ in range(m)]res, end1idx = 0, 0 # 初始化for i in range(m):if str1[i] == str2[0]:dp[i][0] = 1res = 1end1idx = ifor j in range(n):if str2[j] == str1[0]:dp[0][j] = 1# 填表for i in range(1, m):for j in range(1, n):if str1[i] == str2[j]:dp[i][j] = dp[i - 1][j - 1] + 1end1idx = i if res < dp[i][j] else end1idxres = max(res, dp[i][j])return str1[end1idx - res + 1: end1idx + 1]

4.打家劫舍

一般的動態規劃(推薦)

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""# 寫個動態規劃# 狀態:偷到i號房子時的最大值# 選擇:是否偷當前的房子# dp[i]=max(dp[i - 2] + nums[i], dp[i - 1])length = len(nums)if length == 1:return nums[0]dp = [0 for _ in range(length)]dp[0] = nums[0]dp[1] = nums[0] if nums[0] > nums[1] else nums[1]for i in range(2, length):steal = dp[i - 2] + nums[i]not_steal = dp[i - 1]dp[i] = steal if steal > not_steal else not_stealreturn dp[length - 1]

還可以使用自頂向下遞歸+備忘錄。這種方法引入了遞歸,比較適合解決樹結構的問題——打家劫舍III

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""memo = [-1 for _ in range(len(nums))]return self.search(0, nums, memo) # 或memo[0]def search(self, idx, nums, memo):length = len(nums)# 遞歸出口if idx > length - 1:return 0# 查表避免計算elif memo[idx] != -1:return memo[idx]# 后根,先遞歸后返回,以獲取當前序列之后的信息else:steal = nums[idx] + self.search(idx + 2, nums, memo)not_steal = self.search(idx + 1, nums, memo)memo[idx] = max(steal, not_steal)return memo[idx]

5.打家劫舍II

將輸入變成環形,和 I 相比討論兩種情況,取最大值。

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""length = len(nums)if length == 1:return nums[0]elif length <= 3:return max(nums)# 分兩種情況:[1:]和[:-1]nums1 = nums[1: ]nums2 = nums[: -1]dp1 = [0 for _ in range(length - 1)]dp1[0] = nums1[0]dp1[1] = max(nums1[0], nums1[1])for i in range(2, length - 1):dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums1[i])dp2 = [0 for _ in range(length - 1)]dp2[0] = nums2[0]dp2[1] = max(nums2[0], nums2[1])for i in range(2, length - 1):dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums2[i])return max(dp1[-1], dp2[-1])

6.打家劫舍III *

  • 一道典中典的題:動態規劃+樹結構 = memo+遞歸
  • 第二次做的時候思維卡在了,想先獲取某節點的父節點,和父節點的父節點(類似于普通dp的想法)來確定當前點的值,使用遞歸的好處是可以“知道”后續的信息,在當前加以判斷

對于樹結構的問題,明確每個節點該干什么

class Solution(object):def rob(self, root):""":type root: TreeNode:rtype: int"""# dp+樹=memo+遞歸memo = dict()def search(node):# 出口if node is None:return 0# 查表if memo.has_key(node):return memo[node]# 搶l, r = 0, 0if node.left is not None:l = search(node.left.left) + search(node.left.right)if node.right is not None:r = search(node.right.left) + search(node.right.right)yes = node.val + l + r# 不搶no = search(node.left) + search(node.right)# 取最大值寫入memomemo[node] = max(yes, no)return memo[node]return search(root)

7.含冷凍期的股票買賣

給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。?
設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以盡可能地完成更多的交易(多次買賣一支股票):

  • 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
  • 賣出股票后,你無法在第二天買入股票 (即冷凍期為 1 天)。
class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# 狀態:已買/未買/冷凍期 (狀態不要寫成:買入/賣出/冷凍,分清楚狀態和動作)# 選擇:買入/賣出/不變# DFAn = len(prices)have = [0 for _ in range(n)]not_have = [0 for _ in range(n)]freeze = [0 for _ in range(n)]have[0] = -prices[0]for i in range(1, n):# 第i天*持有*股票的最大利潤:昨天持有,今天持有/昨天沒有,今天買入have[i] = max(have[i - 1], not_have[i - 1] - prices[i])# 第i天*不持有*股票的最大利潤:昨天不持有,今天也不持有/昨天賣出,今天冷凍not_have[i] = max(not_have[i - 1], freeze[i - 1])# 第i天***進入冷凍(賣出)***的最大利潤freeze[i] = have[i - 1] + prices[i]# 最后一天處于不持有/剛賣出時的利潤最大return max(not_have[-1], freeze[-1])

股票買賣的最佳時機II *

  • 可以多次交易,但每次只能持有一個股票
  • 狀態DP,加入當前是否已持有股票的狀態
class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# 狀態DP# dp[n][0] 代表截至第n天,不持有股票的最大利潤,dp[n][1]代表持有股票的最大利潤length = len(prices)dp = [[0, 0] for _ in range(length)]dp[0][1] = -prices[0]for i in range(1, length):dp[i][0] = max(dp[i - 1][0], prices[i] + dp[i - 1][1]) # 昨天沒買今天不買/昨天買了今天賣dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]) # 昨天沒買今天買/昨天買了今天不賣return dp[-1][0]

股票買賣的最佳時機III

  • 和上面類似,但限制最多只能交易兩次
  • 需要額外加入兩個狀態
class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# 狀態dp plus# dp[n]:第一位代表截至第n天,第二位代表是否持有股票,第三位代表完成的購買次數length = len(prices)dp = [[[0, 0, 0] for _ in range(2)] for _ in range(length)]# 初始化dp[0][1][1] = -prices[0]# 將不合理位置設置為負值!!!dp[0][1][0] = -1000000dp[0][1][2] = -1000000dp[0][0][1] = -1000000dp[0][0][2] = -1000000# 填表for i in range(1, length):dp[i][1][1] = max(dp[i - 1][1][1], dp[i - 1][0][0] - prices[i]) # 第i天有股票買過一次:可能是之前買的,也可能是今天買,取利潤最大dp[i][1][2] = max(dp[i - 1][1][2], dp[i - 1][0][1] - prices[i]) # 第i天有股票買過兩次:可能是之前買的,也可能是今天買,取利潤最大dp[i][0][1] = max(dp[i - 1][0][1], dp[i - 1][1][1] + prices[i]) # 第i天沒股票買過一次:可能是之前賣的,也可能是今天賣的dp[i][0][2] = max(dp[i - 1][0][2], dp[i - 1][1][2] + prices[i]) # 第i天沒股票買過兩次:可能是之前賣的,也可能是今天賣的return max(dp[-1][0])

8.編輯距離

問題復雜,題解簡介。關鍵是找到DP數組的定義。
選擇思路——引用leetcode下面的高贊評論:

  • 問題1:如果 word1[0…i-1] 到 word2[0…j-1] 的變換需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的變換需要幾步呢?

  • 答:先使用 k 步,把 word1[0…i-1] 變換到 word2[0…j-1],消耗 k 步。再把 word1[i] 改成 word2[j],就行了。如果 word1[i] == word2[j],什么也不用做,一共消耗 k 步,否則需要修改,一共消耗 k + 1 步。

  • 問題2:如果 word1[0…i-1] 到 word2[0…j] 的變換需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的變換需要消耗幾步呢?

  • 答:先經過 k 步,把 word1[0…i-1] 變換到 word2[0…j],消耗掉 k 步,再把 word1[i] 刪除,這樣,word1[0…i] 就完全變成了 word2[0…j] 了。一共 k + 1 步。

  • 問題3:如果 word1[0…i] 到 word2[0…j-1] 的變換需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的變換需要消耗幾步呢?

  • 答:先經過 k 步,把 word1[0…i] 變換成 word2[0…j-1],消耗掉 k 步,接下來,再插入一個字符 word2[j], word1[0…i] 就完全變成了 word2[0…j] 了。

class Solution(object):def minDistance(self, word1, word2):""":type word1: str:type word2: str:rtype: int"""m, n = len(word1), len(word2)dp = [[1000 for _ in range(n + 1)] for _ in range(m + 1)] # dp[i][j]:word1前i個(坐標i - 1)和word2前j個匹配的最小操作數# 初始化for i in range(m + 1):dp[i][0] = i for j in range(n + 1):dp[0][j] = j# 填表for i in range(1, m + 1):for j in range(1, n + 1):# 無需進行任何操作if word1[i - 1] == word2[j - 1]:dp[i][j] = dp[i - 1][j - 1]else:dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])return dp[m][n]

帶權編輯距離

給定兩個字符串str1和str2,再給定三個整數ic,dc和rc,分別代表插入、刪除和替換一個字符的代價,請輸出將str1編輯成str2的最小代價

class Solution:def minEditCost(self , str1 , str2 , ic , dc , rc ):# write code here# dp[i][j] 代表從s1[0...i] 修改為s2[0...j]的代價# dp[i][j] = dp[i-1][j-1] if equals else ...m, n = len(str1), len(str2)dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]for i in range(m + 1):dp[i][0] = i * dc for j in range(n + 1):dp[0][j] = j * ic for i in range(1, m + 1):for j in range(1, n + 1):if str1[i - 1] == str2[j - 1]:dp[i][j] = dp[i - 1][j - 1] else:dp[i][j] = min(dp[i - 1][j] + dc, dp[i][j - 1] + ic, dp[i - 1][j - 1] + rc)return dp[-1][-1]

8.01背包問題

  • 算法設計課上的例題。物品只能選擇裝入/不裝入,所以是01背包。
  • 01背包的問題形式:湊夠目標和target ——(能否)湊夠target,湊target有幾種方式
# dp[i][j] 代表對于物品i,背包容量為j時能承載的最大價值 for i in range(num_items):for j in range(capacity):dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + val[i]) # 選擇裝物品i或者不裝物品i(偽碼,未做防止越界的處理 return dp[-1][-1]

9.分割等和子集(背包問題模板)

抽象成可裝載重量為 sum / 2 的背包,每個物品的重量為 nums[i],在 sum/2 的前提下,盡量往里裝最多的數字,如果恰好能為sum / 2則滿足題意

class Solution(object):def canPartition(self, nums):""":type nums: List[int]:rtype: bool"""# 特判奇偶,奇數不能劃分summary = sum(nums)if summary % 2 == 1:return Falsetarget = summary // 2n = len(nums)dp = [[0 for _ in range(target + 1)] for _ in range(n)]# 初始化首行if nums[0] == target:return Truefor i in range(target + 1):dp[0][i] = nums[0] if i >= nums[0] else 0# 填表:經典的背包模板for i in range(1, n):for j in range(target + 1):# 不能裝下物品iif j < nums[i]:dp[i][j] = dp[i - 1][j]# 可以裝下物品ielse:dp[i][j] = max(dp[i - 1][j], (dp[i - 1][j - nums[i]] if nums[i] <= j else 0) + nums[i])return dp[-1][-1] == target

可以進一步優化空間:因為每行在填寫時只使用上一行dp,所以dp只保留一行即可

10.湊硬幣II(完全背包問題)

  • 處理背包問題一定要注意dp數組的定義,不要少定義下標
  • 完全背包問題的每種物品數量無限
  • 這道題同樣可以對dp數組進行空間優化
class Solution(object):def change(self, amount, coins):""":type amount: int:type coins: List[int]:rtype: int"""# 湊和,背包問題;每種物品無限,完全背包問題# dp[i][j]:前i個硬幣湊和jlength = len(coins)dp = [[0 for _ in range(amount + 1)] for _ in range(length)]# 初始化:湊0元有1種方式for i in range(length):dp[i][0] = 1# 初始化:只用首個硬幣for j in range(1, amount + 1):if j % coins[0] == 0:dp[0][j] = 1# 填表for i in range(1, length):for j in range(amount + 1):dp[i][j] = dp[i - 1][j] # 不用第i個硬幣dp[i][j] += dp[i][j - coins[i]] if (j - coins[i] >= 0) else 0 # 用第i個硬幣return dp[-1][-1]

11.目標和(01背包)

給你一個整數數組 nums 和一個整數 target 。 向數組中的每個整數前添加 ‘+’ 或 ‘-’ ,然后串聯起所有整數,可以構造一個
表達式 : 例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串聯起來得到表達式
“+2-1” 。 返回可以通過上述方法構造的、運算結果等于 target 的不同 表達式 的數目。

class Solution(object):def findTargetSumWays(self, nums, target):""":type nums: List[int]:type target: int:rtype: int"""# dp[i][j]:前i個數字加減 得到總和j 的方法數目# dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]"""!!!!數組的列范圍 要開到[-sum(nums), sum(nums)]而非[-target, target]!!!!"""# 特判summary = sum(nums)if summary < target:return 0n = len(nums)dp = [[0 for _ in range(2 * summary + 1)] for _ in range(n)] # 包含負數,下標target處為0# 初始化首行for j in range(2 * summary + 1):if nums[0] == 0:dp[0][summary] = 2breakelse:real_val = j - summaryif nums[0] == real_val or -nums[0] == real_val:dp[0][j] = 1# 填表for i in range(1, n):for j in range(2 * summary + 1):minus = j - nums[i]plus = j + nums[i]dp[i][j] += dp[i - 1][minus] if minus >= 0 else dp[i - 1][0]dp[i][j] += dp[i - 1][plus] if plus <= 2 * summary else dp[i - 1][0]return dp[-1][summary + target]

12 n個骰子的點數

  • 把n個骰子扔在地上,所有骰子朝上一面的點數之和為s。輸入n,打印出s的所有可能的值出現的概率
class Solution(object):def dicesProbability(self, n):""":type n: int:rtype: List[float]"""# dp[i][j] = 前i個骰子點數和為j的概率# dp[i][j] = sigma(dp[i - 1][j - k]), k = 1...6dp = [[0 for _ in range(6 * n + 1)] for _ in range(n)]# 初始化for i in range(1, 7):dp[0][i] = 0.16667# 填表for i in range(1, n):for j in range(i + 1, 6 * i + 7):for k in range(1, 7):dp[i][j] += dp[i - 1][j - k] / 6 if j - k > 0 else 0return dp[-1][n:]

總結

以上是生活随笔為你收集整理的算法 - 动态规划的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。