欧美一区二区三区,国内熟女精品熟女A片视频小说,日本av网,小鲜肉男男GAY做受XXX网站

嵌套查詢 mysql,如何學(xué)習(xí)數(shù)據(jù)分析

動(dòng)態(tài)規(guī)劃如何測(cè)試是否超時(shí)?

動(dòng)態(tài)規(guī)劃法(dynamic programming)通常用于求解最優(yōu)化問(wèn)題(optimization problem),它適用于那些子問(wèn)題相互重疊的情況,即子問(wèn)題不獨(dú)立,不同的子問(wèn)題具有公共的子子問(wèn)題(就是子問(wèn)題的子問(wèn)題)。這顯然與分治法是不同的,分治法將問(wèn)題劃分為不重疊的子問(wèn)題,然后分別求解這些子問(wèn)題,最后將這些問(wèn)題合并得到最終的解。

對(duì)于具有公共子問(wèn)題的情況,分治法會(huì)做很多不必要的工作,它會(huì)多次求解同一子子問(wèn)題。動(dòng)態(tài)規(guī)劃法卻不一樣,對(duì)每個(gè)子子問(wèn)題它只會(huì)求解一次,將其保存在一個(gè)表格中,避免了不必要的重復(fù)計(jì)算。

利用動(dòng)態(tài)規(guī)劃法求出來(lái)的是這個(gè)問(wèn)題的一個(gè)最優(yōu)解(an optimal solution),記住這里求解的只是最優(yōu)解(the optimal solution)中的一個(gè),因?yàn)樽顑?yōu)解可能有多個(gè)。

適用dp的問(wèn)題必須滿足最優(yōu)化原理和無(wú)后效性。

1.最優(yōu)化原理:如果問(wèn)題的最優(yōu)解包含的子問(wèn)題的解也是最優(yōu)解,則稱該問(wèn)題具有最有子結(jié)構(gòu),即滿足最優(yōu)化原理。(也即子結(jié)構(gòu)最優(yōu)時(shí)通過(guò)選擇后一定最優(yōu))

2.無(wú)后效性:某階段的狀態(tài)一旦確定,則此后過(guò)程的演變不再受此前各種狀態(tài)及決策的影響,簡(jiǎn)單的說(shuō),就是“未來(lái)與過(guò)去無(wú)關(guān)”,當(dāng)前的狀態(tài)是此前歷史的一個(gè)完整總結(jié),此前的歷史只能通過(guò)當(dāng)前的狀態(tài)去影響過(guò)程未來(lái)的演變。

3.重疊子問(wèn)題:即子問(wèn)題之間是不獨(dú)立的,一個(gè)子問(wèn)題在下一階段決策中可能被多次使用到。(該性質(zhì)并不是動(dòng)態(tài)規(guī)劃適用的必要條件,但是如果沒(méi)有這條性質(zhì),動(dòng)態(tài)規(guī)劃算法同其他算法相比就不具備優(yōu)勢(shì))。其實(shí)本質(zhì)上dp就是一個(gè)以空間換時(shí)間的做法,為了降低時(shí)間復(fù)雜度,不對(duì)子問(wèn)題進(jìn)行重復(fù)計(jì)算,其必須存儲(chǔ)過(guò)程中的各種狀態(tài)。

動(dòng)態(tài)規(guī)劃可以從暴力枚舉逐步優(yōu)化得到。問(wèn)題求解的各個(gè)方法:暴力枚舉->遞歸->備忘錄算法->動(dòng)歸算法

[leetcode]10.Regular Expression Matching

對(duì)于p字符串有點(diǎn)、字母、點(diǎn).

.

、字母?

?

四種元素,點(diǎn)匹配任意一個(gè)字母,字母匹配相同的一個(gè)字母,點(diǎn)?

?

匹配任意字母(可以是任意不同字母,例如.?

.

?

匹配abc),字母?

?

匹配連續(xù)任意個(gè)相同字母,值得注意的是?

?

的任意包括0個(gè)。由于?

?

可以匹配任意個(gè),造成檢驗(yàn)s和p是否完全匹配的時(shí)候難以確定究竟?

?

匹配幾個(gè)字母合適,這正是本題的關(guān)鍵點(diǎn)。

class Solution(object):

def isMatch(self, s, p):

"""

:type s: str

:type p: str

:rtype: bool

"""

# dynamic programming

len_s = len(s)

len_p = len(p)

# initialize

dp = [[False for i in range (len_p+1)] for j in range(len_s+1)]

dp[0][0] = True

# initialize dp[0][j]

for j in range(1,len_p):

if p[j] == '*':

dp[0][j+1] = dp [0][j-1]

for i in range(1,len_s+1):

for j in range(1,len_p+1):

if s[i-1] == p[j-1] or p[j-1] == '.':

dp[i][j] = dp[i-1][j-1]

elif p[j-1] == '*':

dp[i][j] = (dp[i-1][j-2] and (p[j-2] == s[i-1] or p[j-2] == '.')) or dp[i][j-2] or

(dp[i-1][j] and (s[i-1] == p[j-2] or p[j-2] =='.'))

return dp[len_s][len_p]

相似的題目還有:[leetcode]44.Wildcard Matching;

[leetcode]62.Unique Paths

動(dòng)態(tài)規(guī)劃的關(guān)鍵是要得到遞推關(guān)系式。對(duì)于本題,到達(dá)某一點(diǎn)的路徑數(shù)等于到達(dá)它上一點(diǎn)的路徑數(shù)與它左邊的路徑數(shù)之和。也即,起點(diǎn)到點(diǎn)(i, j)的路徑總數(shù):ways[i][j] = 起點(diǎn)到點(diǎn)(i, j-1)的總數(shù):ways[i][j-1] + 起點(diǎn)到點(diǎn)(i-1, j)總數(shù):ways[i-1][j]。于是我們就得到遞推關(guān)系式:????[?][?]=????[?][??1]+????[??1][?]

w

a

y

s

[

i

]

[

j

]

=

w

a

y

s

[

i

]

[

j

?

1

]

+

w

a

y

s

[

i

?

1

]

[

j

]

class Solution:

def uniquePaths(self, m, n):

paths = [[1 for i in range(n)] for i in range(m)]

for i in range(1,m):

for j in range(1,n):

paths[i][j] = paths[i-1][j]+paths[i][j-1]

return paths[m - 1][n - 1]

相似的題目還有:[leetcode]63.Unique Paths II;[leetcode]64.Minimum Path Sum

[leetcode]72.Edit Distance

編輯距離也是一個(gè)極其重要和經(jīng)典的問(wèn)題了。

這個(gè)算法計(jì)算的是將s[1…i]轉(zhuǎn)換為t[1…j](例如將kitten轉(zhuǎn)換為sitting)所需最少的操作數(shù)(也就是所謂的編輯距離),這個(gè)操作數(shù)被保存在d[i,j](d代表的就是上圖所示的二維數(shù)組)中。

在第一行與第一列肯定是正確的,這也很好理解,例如我們將kitten轉(zhuǎn)換為空字符串,我們需要進(jìn)行的操作數(shù)為kitten的長(zhǎng)度(所進(jìn)行的操作為將kitten所有的字符丟棄)。

我們對(duì)字符可能進(jìn)行的操作有三種:

如果我們可以使用k個(gè)操作數(shù)把s[1…i]轉(zhuǎn)換為t[1…j-1],我們只需要把t[j]加在最后面就能將s[1…i]轉(zhuǎn)換為t[1…j],操作數(shù)為k+1

如果我們可以使用k個(gè)操作數(shù)把s[1…i-1]轉(zhuǎn)換為t[1…j],我們只需要把s[i]從最后刪除就可以完成轉(zhuǎn)換,操作數(shù)為k+1

如果我們可以使用k個(gè)操作數(shù)把s[1…i-1]轉(zhuǎn)換為t[1…j-1],我們只需要在需要的情況下(s[i] != t[j])把s[i]替換為t[j],所需的操作數(shù)為k+cost(cost代表是否需要轉(zhuǎn)換,如果s[i]==t[j],則cost為0,否則為1)。

將s[1…n]轉(zhuǎn)換為t[1…m]當(dāng)然需要將所有的s轉(zhuǎn)換為所有的t,所以,d[n,m](表格的右下角)就是我們所需的結(jié)果。

class Solution:

def minDistance(self, word1: str, word2: str) -> int:

m,n = len(word1),len(word2)

dp = [[0 for j in range(n+1)] for i in range(m+1)]

for i in range(m+1):

for j in range(n+1):

if i==j==0:

dp[0][0] = 0

elif i==0:

dp[0][j] = j

elif j==0:

dp[i][0] = i

else:

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[-1][-1]

[leetcode]91.Decode Ways

很顯然,由于本題只要求有多少種decode的方法,而不要求把每種方法都列出來(lái),所以用DP。假設(shè)當(dāng)前的元素為Z,他前面的兩個(gè)為XY, i.e.XYZ。

1.如果Z=0, 并且Y=1 or 2。 那么對(duì)應(yīng)于Z的DP值等于X的DP值,因?yàn)閷?duì)于10只有一種解釋方式,所以 DP[i] = DP[i-2]。

2.如果 YZ 位于[11, 19] 或者 [21, 26] 這兩個(gè)range中,那么顯然對(duì)于這個(gè)兩位數(shù)我們有兩種decode的方式,也就是說(shuō) DP[i] = DP[i-1]+DP[i-2], 注意這里不是DP[i] = DP[i-1]+1。 例如 1212中的最后一個(gè)2。

3.如果X不是0, 例如YZ = 81。 那么DP[i] = DP[i-1].

最后注意的是由于2中我們要取到DP[i-2],所以我們?cè)诔跏糄P list的時(shí)候在最前面加上一個(gè)1。由于加上了最前面的這個(gè)1。所以當(dāng)DP[i]對(duì)應(yīng)的是s[i-1]。 YZ對(duì)應(yīng)的就是 s[(i-1)-1:(i-1)+1]。

class Solution:

def numDecodings(self, s: str) -> int:

if not s or s[0] == '0':

return 0

dp = [1 for i in range(len(s)+1)]

for i in range(2,len(s)+1):

nums = s[i-2:i]

if 1<int(nums)<10 :

dp[i] = dp[i-1]

elif 11<=int(nums)<=19 or 21<=int(nums)<=26:

dp[i] = dp[i-1]+dp[i-2]

elif int(nums) == 10 or int(nums) == 20:

dp[i] = dp[i-2]

elif nums[-1]=='0':

dp[i] = 0

else:

dp[i] = dp[i-1]

return dp[-1]

[leetcode]97.Interleaving String

可以用遞歸做,每匹配s1或者s2中任意一個(gè)就遞歸下去。但是會(huì)超時(shí)。

因此考慮用動(dòng)態(tài)規(guī)劃做。

s1, s2只有兩個(gè)字符串,因此可以展平為一個(gè)二維地圖,判斷是否能從左上角走到右下角。

當(dāng)s1到達(dá)第i個(gè)元素,s2到達(dá)第j個(gè)元素:

地圖上往右一步就是s2[j-1]匹配s3[i+j-1]。

地圖上往下一步就是s1[i-1]匹配s3[i+j-1]。

class Solution:

def isInterleave(self, s1: str, s2: str, s3: str) -> bool:

if not s1:return s2==s3

if not s2:return s1==s3

m,n = len(s1),len(s2)

if m+n != len(s3):return False

dp = [[False for j in range(n+1)] for i in range(m+1)]

for i in range(m+1):

for j in range(n+1):

if i==j==0:

dp[i][j] = True

elif i == 0:

dp[0][j] = (dp[0][j-1] and s2[j-1] == s3[j-1])

elif j == 0:

dp[i][0] = (dp[i-1][0] and s1[i-1] == s3[i-1])

else:

dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])

return dp[m][n]

[leetcode]115.Distinct Subsequences

互異子序列。用dp[i][j]記錄S的前i個(gè)和T的前j個(gè)的符合個(gè)數(shù),那么最后目標(biāo)就是dp[S.size()][T.size()];

初始化,j = 0 時(shí)候,dp[i][0] = 1,因?yàn)樗械亩伎梢酝ㄟ^(guò)刪除所有變成空字符,并且只有一種。

遞推式子如下了:

i和j都從1開(kāi)始,且j不能大于i,因?yàn)槠ヅ涞拈L(zhǎng)度至少為1開(kāi)始,j大于i無(wú)意義

如果 ?==?

i

==

j

那么 dp[i][j] = S.substr(0, i) == T.substr(0, j);

如果 ?!=?

i

!

=

j

分兩種情況

S[i-1] != T[j-1] 時(shí),也就是加入不加入i的影響是一樣的,那么 dp[i][j] = dp[i - 1][j];

S[i-1] == T[j-1] 時(shí),那么當(dāng)前字符可選擇匹配或者是不匹配,所以dp[i][j] = dp[i - 1][j -1] + dp[i - 1][j];

class Solution:

def numDistinct(self, s: str, t: str) -> int:

m,n = len(s),len(t)

dp = [[0 for j in range(m+1)] for i in range(n+1)]

dp[0][0] = 1

for j in range(1,m+1):

dp[0][j] = 1

for i in range(1,n+1):

for j in range(1,m+1):

if t[i-1] == s[j-1]:

dp[i][j] = dp[i-1][j-1]+dp[i][j-1]

else:

dp[i][j] = dp[i][j-1]

return dp[n][m]

[leetcode]123.Best Time to Buy and Sell Stock III

這道是買股票的最佳時(shí)間系列問(wèn)題中最難最復(fù)雜的一道,前面兩道 Best Time to Buy and Sell Stock 和 Best Time to Buy and Sell Stock II 的思路都非常的簡(jiǎn)潔明了,算法也很簡(jiǎn)單。而這道是要求最多交易兩次,找到最大利潤(rùn),還是需要用動(dòng)態(tài)規(guī)劃Dynamic Programming來(lái)解,而這里我們需要兩個(gè)遞推公式來(lái)分別更新兩個(gè)變量local和global.我們其實(shí)可以求至少k次交易的最大利潤(rùn),找到通解后可以設(shè)定 k = 2,即為本題的解答。我們定義local[i][j]為在到達(dá)第i天時(shí)最多可進(jìn)行j次交易并且最后一次交易在最后一天賣出的最大利潤(rùn),此為局部最優(yōu)。然后我們定義global[i][j]為在到達(dá)第i天時(shí)最多可進(jìn)行j次交易的最大利潤(rùn),此為全局最優(yōu)。它們的遞推式為:

local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)

global[i][j] = max(local[i][j], global[i - 1][j])

其中局部最優(yōu)值是比較前一天并少交易一次的全局最優(yōu)加上大于0的差值,和前一天的局部最優(yōu)加上差值中取較大值,而全局最優(yōu)比較局部最優(yōu)和前一天的全局最優(yōu),代碼如下:

class Solution:

def maxProfit(self, prices: List[int]) -> int:

if not prices:

return 0

# 二維數(shù)組

# k = 2

# local = [[0 for j in range(k+1)] for i in range(len(prices))]

# glob = [[0 for j in range(k+1)] for i in range(len(prices))]

# for i in range(1,len(prices)):

# diff = prices[i]-prices[i-1]

# for j in range(1,k+1):

# local[i][j] = max(local[i-1][j]+diff,glob[i-1][j-1]+diff)

# glob[i][j] = max(glob[i-1][j],local[i][j])

# return glob[-1][-1]

# 一維數(shù)組

k = 2

local = [0 for j in range(k+1)]

glob = [0 for j in range(k+1)]

for i in range(1,len(prices)):

diff = prices[i]-prices[i-1]

for j in reversed(range(1,k+1)):

local[j] = max(local[j]+diff,glob[j-1]+diff)

glob[j] = max(glob[j],local[j])

return glob[-1]

其實(shí)可以總結(jié)為兩個(gè)狀態(tài)的DP,分別代表當(dāng)天必須賣的利潤(rùn)和不一定當(dāng)天賣的利潤(rùn)。

[leetcode]132.Palindrome Partitioning II

輸入一個(gè)字符串,將其進(jìn)行分割,分割后各個(gè)子串必須是“回文”結(jié)構(gòu),要求最少的分割次數(shù)。顯然,為了求取最少分割次數(shù),一個(gè)簡(jiǎn)單的思路是窮盡所有分割情況,再?gòu)闹姓页龇指詈罂蓸?gòu)成回文子串且次數(shù)最少的分割方法。

對(duì)于一個(gè)字符串,我們需要考慮所有可能的分割,這個(gè)問(wèn)題可以抽象成一個(gè)DP問(wèn)題,對(duì)于一個(gè)長(zhǎng)度為n的字符串,設(shè)DP[i][j]表示第i個(gè)字符到第j個(gè)字符是否構(gòu)成回文,若是,則DP[i][j]=1;若否,則DP[i][j]=0

class Solution:

def minCut(self, s: str) -> int:

n = len(s)

dp = [(i-1) for i in range(n + 1)]

for i in range(n + 1):

for j in range(i):

tmp = s[j:i]

if tmp == tmp[::-1]:

dp[i] = min(dp[i], dp[j] + 1)

return dp[n]

[leetcode]139.Word Break

動(dòng)態(tài)規(guī)劃。dp[i]表示字符串s[:i]能否拆分成符合要求的子字符串。我們可以看出,如果s[j:i]在給定的字符串組中,且dp[j]為True(即字符串s[:j]能夠拆分成符合要求的子字符串),那么此時(shí)dp[i]也就為True了。

class Solution:

def wordBreak(self, s: str, wordDict: List[str]) -> bool:

dp = [False for i in range(len(s)+1)]

dp[0] = True

for i in range(len(s)+1):

for k in range(i):

if dp[k] and s[k:i] in wordDict:

dp[i] = True

# break

return dp[len(s)]

以上關(guān)于字符串的動(dòng)態(tài)規(guī)劃,我們可以發(fā)現(xiàn):對(duì)于字符串求最長(zhǎng)/短,最大/小的最優(yōu)解,我們可以通過(guò)分析子問(wèn)題來(lái)分解問(wèn)題的求解,通過(guò)子問(wèn)題的求解來(lái)完成整體的最優(yōu)解。

[leetcode]174.Dungeon Game

自底向上的動(dòng)態(tài)規(guī)劃

在走完最后一個(gè)房間的時(shí)候血量至少要剩下1,因此最后的狀態(tài)可以當(dāng)成是初始狀態(tài),由后往前依次決定在每一個(gè)位置至少要有多少血量, 這樣一個(gè)位置的狀態(tài)是由其下面一個(gè)和和左邊一個(gè)的較小狀態(tài)決定 .因此一個(gè)基本的狀態(tài)方程是: dp[i][j] = min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j].

class Solution:

def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:

m, n = len(dungeon), len(dungeon[0])

dp = [[0 for j in range(n)]for i in range(m)]

for i in reversed(range(m)):

for j in reversed(range(n)):

if i == m-1 and j == n-1:

dp[i][j] = max(0, -dungeon[i][j])

elif i == m-1:

dp[i][j] = max(0, dp[i][j+1] - dungeon[i][j])

elif j == n-1:

dp[i][j] = max(0, dp[i+1][j] - dungeon[i][j])

else:

dp[i][j] = max(0, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])

return dp[0][0] + 1

[leetcode]198.House Robber

動(dòng)態(tài)規(guī)劃DP。本質(zhì)相當(dāng)于在一列數(shù)組中取出一個(gè)或多個(gè)不相鄰數(shù),使其和最大。

class Solution:

def rob(self, nums: List[int]) -> int:

if not nums:return 0

dp = [0 for i in range(len(nums)+1)]

for i in range(1,len(nums)+1):

if i==1:

dp[1] = nums[0]

else:

dp[i] = max(dp[i-1],dp[i-2]+nums[i-1])

return dp[-1]

[leetcode]213.House Robber II

這個(gè)題多了環(huán)的條件,在這個(gè)約束下就多了個(gè)不同時(shí)偷第一個(gè)和最后一個(gè)就可以了。所以,兩種偷的情況:第一種不偷最后一個(gè)房間,第二種不偷第一個(gè)房間,求這兩種偷法能獲得的最大值。

class Solution:

def rob(self, nums: List[int]) -> int:

if not nums:return 0

elif len(nums)==1:return nums[0]

elif len(nums)==2:return max(nums[0],nums[1])

else:

return max(self._rob(nums[1:]),self._rob(nums[:-1]))

def _rob(self,nums):

if not nums:return 0

dp = [0 for i in range(len(nums)+1)]

dp[1] = nums[0]

for i in range(2,len(nums)+1):

dp[i] = max(dp[i-1],dp[i-2]+nums[i-1])

return dp[-1]

[leetcode]221.Maximal Square

動(dòng)態(tài)規(guī)劃,令A(yù)[i][j]表示的就是以(i,j)為右下角的最大的正方形的邊長(zhǎng),時(shí)間復(fù)雜程度是?(???)

O

(

M

?

N

)

,空間復(fù)雜程度是?(???)

O

(

M

?

N

)

class Solution:

def maximalSquare(self, matrix: List[List[str]]) -> int:

if not matrix:return 0

m,n = len(matrix),len(matrix[0])

dp = [[0 for j in range(n)] for i in range(m)]

res = 0

for i in range(m):

for j in range(n):

if i == 0:

dp[i][j] = int(matrix[i][j])

elif j == 0:

dp[i][j] = int(matrix[i][j])

else:

if matrix[i][j] == '1':

dp[i][j] = min(dp[i-1][j- 1],dp[i-1][j],dp[i][j- 1])+1

else:

dp[i][j] = 0

res = max(res,dp[i][j])

return res*res

注意這里dp[i][j]的更新條件是,matrix[i][j] == '1',更新為dp[i-1][j- 1],dp[i-1][j],dp[i][j- 1]的最小值+1

[leetcode]309.Best Time to Buy and Sell Stock with Cooldown

兩個(gè)狀態(tài):

1.在第i天買一支股票還能剩下的利潤(rùn)=第(i-2)天銷售能夠剩余的利潤(rùn)-第i天股票的價(jià)錢.

2.在第i天賣一支股票總的利潤(rùn)=第(i-1)天買股票剩下的最大利潤(rùn)+當(dāng)前股票的價(jià)格.

也就是說(shuō)需要維護(hù)兩個(gè)狀態(tài)的信息,一個(gè)是買股票所得到的剩余最大利潤(rùn),一個(gè)是賣出股票之后得到的最大利潤(rùn),他們互相依賴對(duì)方的信息.

再來(lái)進(jìn)一步分析如何維持一個(gè)最大的利潤(rùn).

對(duì)于買來(lái)說(shuō),當(dāng)天是否買取決于買了之后是否比之前買所剩余的利潤(rùn)大,即狀態(tài)轉(zhuǎn)移方程為:

buy[i] = max(buy[i-1], sell[i-2] - prices[i-1]);

對(duì)于賣來(lái)說(shuō),同樣當(dāng)天是否將這只股票賣掉取決于賣掉能否獲得更大的利潤(rùn),狀態(tài)轉(zhuǎn)移方程為:

sell[i] = max(sell[i-1], buy[i-1] + prices[i-1]);

也可以根據(jù)題意,維護(hù)三個(gè)狀態(tài),更容易理解。

class Solution:

def maxProfit(self, prices: List[int]) -> int:

# if not prices:

# return 0

# N = len(prices)

# buy = [0 for i in range(N+1)]

# sell = [0 for i in range(N+1)]

# buy[1] = -prices[0]

# for i in range(2,N+1):

# buy[i] = max(sell[i-2]-prices[i-1],buy[i-1])

# sell[i] = max(sell[i-1],buy[i-1]+prices[i-1])

# return sell[N]

if not prices:

return 0

N = len(prices)

rest = [0 for i in range(N)]

buy = [0 for i in range(N)]

sell = [0 for i in range(N)]

buy[0] = -prices[0]

for i in range(1,N):

rest[i] = max(rest[i-1],sell[i-1])

buy[i] = max(buy[i-1],rest[i-1]-prices[i])

sell[i] = max(sell[i-1],buy[i-1]+prices[i])

return sell[-1]

[leetcode]312.Burst Balloons

動(dòng)態(tài)規(guī)劃

設(shè)dp[i][j]為i到j(luò)這段區(qū)間所能得到的最大值,狀態(tài)轉(zhuǎn)移方程為dp[i][j] = max(i < k < j) (dp[i][k] + dp[k][j] + a[i] * a[k] * a[j])

class Solution:

def maxCoins(self, nums: List[int]) -> int:

nums = [1] + nums + [1]

n = len(nums)

dp = [[0 for j in range(n)] for i in range(n)]

# dp[i][j]代表(i,j)區(qū)間內(nèi)取得的最大值 -->關(guān)鍵在于理解dp內(nèi)涵

for left in reversed(range(n)):

for right in range(left+1,n):

for i in range(left + 1, right):

dp[left][right] = max(dp[left][right],nums[left] * nums[i] * nums[right] +dp[left][i] + dp[i][right])

return dp[0][-1]

[leetcode]368.Largest Divisible Subset

使用一個(gè)一維DP,其含義是題目要求的數(shù)組,DP[i]的含義是,從0~i位置滿足題目的最長(zhǎng)數(shù)組。先用i遍歷每個(gè)數(shù)字,然后用j從后向前尋找能被nums[i]整除的數(shù)字,這樣如果判斷能整除的時(shí)候,再判斷dp[i] < dp[j] + 1,即對(duì)于以i索引結(jié)尾的最長(zhǎng)的數(shù)組是否變長(zhǎng)了。在變長(zhǎng)的情況下,需要更新dp[i],同時(shí)使用parent[i]更新i的前面能整除的數(shù)字。另外還要統(tǒng)計(jì)對(duì)于整個(gè)數(shù)組最長(zhǎng)的子數(shù)組長(zhǎng)度。

知道了對(duì)于每個(gè)位置最長(zhǎng)的子數(shù)組之后,我們也就知道了對(duì)于0~n區(qū)間內(nèi)最長(zhǎng)的滿足題目條件的數(shù)組,最后需要再次遍歷,使用parent就能把正兒個(gè)數(shù)組統(tǒng)計(jì)輸出出來(lái)。因?yàn)檫@個(gè)最大的索引mx_index是對(duì)n而言的,所以輸出是逆序的。

class Solution:

def largestDivisibleSubset(self, nums: List[int]) -> List[int]:

if not nums: return []

N = len(nums)

nums.sort()

dp = [1] * N #LDS

parent = [0] * N

mx = 1

mx_index = -1

for i in range(N):

for j in reversed(range(i)):

if nums[i] % nums[j] == 0 and dp[i] < dp[j] + 1:

dp[i] = dp[j] + 1

parent[i] = j

if dp[i] > mx:

mx = dp[i]

mx_index = i

res = []

for k in range(mx):

res.append(nums[mx_index])

mx_index = parent[mx_index]

return res[::-1]

這里的解題技巧是,在動(dòng)態(tài)規(guī)劃同時(shí)保存狀態(tài),并且使用額外空間恢復(fù)現(xiàn)場(chǎng)。

[leetcode]486.Predict the Winner

動(dòng)態(tài)規(guī)劃

1.該問(wèn)題沒(méi)有直接比較一個(gè)選手所拿元素的和值,而是把問(wèn)題轉(zhuǎn)換為兩個(gè)選手所拿元素的差值。這一點(diǎn)很巧妙,是關(guān)鍵的一步。

2.找出遞推表達(dá)式:max(nums[beg] - partition(beg + 1, end), nums[end] - partition(beg, end + 1))

3.通過(guò)遞推表達(dá)式構(gòu)造遞歸算法是比較簡(jiǎn)單的。但是要構(gòu)造一個(gè)非遞歸的算法難度較大。對(duì)于非遞歸算法,首先在dp中賦初始值,這是我們解題的第一步。在這個(gè)問(wèn)題中,我們使用一個(gè)二位的數(shù)組dp來(lái)表示nums數(shù)組中任意開(kāi)始和結(jié)束位置兩人結(jié)果的差值。

初始的時(shí)候,我們僅僅知道對(duì)角線上的值。dp[i][i] = nums[i].這一點(diǎn)很好理解。接下來(lái)既然是求任意的開(kāi)始和結(jié)束,對(duì)于二維數(shù)組,那肯定是一個(gè)雙層的循環(huán)。通過(guò)dp中已知的元素和動(dòng)態(tài)規(guī)劃的遞推表達(dá)式,我們就可以構(gòu)造出我們的需要的結(jié)果。非遞歸的方式是從小問(wèn)題到大問(wèn)題的過(guò)程。

class Solution(object):

def PredictTheWinner(self, nums):

"""

:type nums: List[int]

:rtype: bool

"""

n = len(nums)

dp_f = [[0 for j in range(n)] for i in range(n)]

dp_g = [[0 for j in range(n)] for i in range(n)]

for i in reversed(range(n)):

for j in range(i,n):

if i==j:

dp_f[i][j] = nums[i]

else:

dp_f[i][j] = max(nums[i]+dp_g[i+1][j],

nums[j]+dp_g[i][j-1])

dp_g[i][j] = min(dp_f[i+1][j],dp_f[i][j-1])

return dp_f[0][-1] >= sum(nums)/2

[leetcode]494.Target Sum

該問(wèn)題求解數(shù)組中數(shù)字只和等于目標(biāo)值的方案?jìng)€(gè)數(shù),每個(gè)數(shù)字的符號(hào)可以為正或負(fù)(減整數(shù)等于加負(fù)數(shù))。由于target和sum(nums)是固定值,因此原始問(wèn)題轉(zhuǎn)化為求解nums中子集的和等于sum(P)的方案?jìng)€(gè)數(shù)問(wèn)題,可轉(zhuǎn)化為背包問(wèn)題求解。

class Solution:

def findTargetSumWays(self, nums: List[int], S: int) -> int:

sum_ = sum(nums)

target = (sum_+S)/2

if target!=int(target):

return 0

if target>sum(nums):

return 0

target = int(target)

dp = [0 for i in range(target+1)]

dp[0] =1

for num in nums:

for i in reversed(range(num,target+1)):

dp[i] += dp[i-num]

return dp[-1]

[leetcode]516.Longest Palindromic Subsequence

設(shè)立一個(gè)len行l(wèi)en列的dp數(shù)組~dp[i][j]表示字符串i~j下標(biāo)所構(gòu)成的子串中最長(zhǎng)回文子串的長(zhǎng)度~最后我們需要返回的是dp[0][len-1]的值。

dp數(shù)組更新:首先i指針從尾到頭遍歷,j指針從i指針后面一個(gè)元素開(kāi)始一直遍歷到尾部~一開(kāi)始dp[i][i]的值都為1,如果當(dāng)前i和j所指元素相等,說(shuō)明能夠加到i~j的回文子串的長(zhǎng)度中,所以更新dp[i][j] = dp[i+1][j-1] + 2; 如果當(dāng)前元素不相等,那么說(shuō)明這兩個(gè)i、j所指元素對(duì)回文串無(wú)貢獻(xiàn),則dp[i][j]就是從dp[i+1][j]和dp[i][j-1]中選取較大的一個(gè)值即可。

class Solution:

def longestPalindromeSubseq(self, s: str) -> int:

if not s:

return 0

n = len(s)

dp = [[0 for j in range(n)] for i in range(n)]

for i in reversed(range(n)):

for j in range(i,n):

if i == j:dp[i][j] = 1

elif s[i] == s[j]:

dp[i][j] = dp[i+1][j-1]+2

else:

dp[i][j] = max(dp[i+1][j],dp[i][j-1])

return dp[0][-1]

[leetcode]576.Out of Boundary Paths

這道題給了我們一個(gè)二維的數(shù)組,某個(gè)位置放個(gè)足球,每次可以在上下左右四個(gè)方向中任意移動(dòng)一步,總共可以移動(dòng)N步,問(wèn)我們總共能有多少種移動(dòng)方法能把足球移除邊界,由于結(jié)果可能是個(gè)巨大的數(shù),所以讓我們對(duì)一個(gè)大數(shù)取余。那么我們知道對(duì)于這種結(jié)果很大的數(shù)如果用遞歸解法很容易爆棧,所以最好考慮使用DP來(lái)解。那么我們使用一個(gè)三維的DP數(shù)組,其中dp[k][i][j]表示總共走k步,從(i,j)位置走出邊界的總路徑數(shù)。那么我們來(lái)找遞推式,對(duì)于dp[k][i][j],走k步出邊界的總路徑數(shù)等于其周圍四個(gè)位置的走k-1步出邊界的總路徑數(shù)之和,如果周圍某個(gè)位置已經(jīng)出邊界了,那么就直接加上1,否則就在dp數(shù)組中找出該值,這樣整個(gè)更新下來(lái),我們就能得出每一個(gè)位置走任意步數(shù)的出界路徑數(shù)了,最后只要返回dp[N][i][j]就是所求結(jié)果了。

class Solution:

def findPaths(self, m: int, n: int, N: int, i: int, j: int) -> int:

dp = [[0] * n for _ in range(m)]

for s in range(1, N + 1):

curStatus = [[0] * n for _ in range(m)]

for x in range(m):

for y in range(n):

v1 = 1 if x == 0 else dp[x - 1][y]

v2 = 1 if x == m - 1 else dp[x + 1][y]

v3 = 1 if y == 0 else dp[x][y - 1]

v4 = 1 if y == n - 1 else dp[x][y + 1]

curStatus[x][y] = (v1 + v2 + v3 + v4) % (10**9 + 7)

dp = curStatus

return dp[i][j]

背包問(wèn)題

[leetcode]322.Coin Change

使用動(dòng)態(tài)規(guī)劃,需要構(gòu)建一個(gè)長(zhǎng)度是amount + 1的dp數(shù)組,其含義是能夠成面額從0到amount + 1需要使用的最少硬幣數(shù)量。

class Solution:

def coinChange(self, coins: List[int], amount: int) -> int:

"""

:type coins: List[int]

:type amount: int

:rtype: int

"""

dp = [float('inf') for i in range(amount + 1)]

dp[0] = 0

for coin in coins:

for i in range(coin, amount + 1):

# if dp[i - coin] != float('inf'):

dp[i] = min(dp[i], dp[i - coin] + 1)

return dp[amount] if dp[amount]!= float('inf') else -1

[leetcode]474.Ones and Zeroes

這道題是從字符串集合中盡可能多的選出字符串并保證0和1個(gè)數(shù)不超過(guò)給定值,題目難度為Medium。

題目和0-1背包問(wèn)題大同小異,區(qū)別是這里限制0和1個(gè)數(shù),而0-1背包問(wèn)題限制總重量,算是動(dòng)態(tài)規(guī)劃的經(jīng)典題目。

這里用dp[i][j][k]表示前i個(gè)字符串在0個(gè)數(shù)不超過(guò)j、1個(gè)數(shù)不超過(guò)k時(shí)最多能選取的字符串個(gè)數(shù)。統(tǒng)計(jì)第i個(gè)字符串中0和1個(gè)數(shù)分別為cnt0和cnt1,如果取第i個(gè)字符串則dp[i][j][k] = dp[i-1][j-cnt0][k-cnt1] + 1,如果不取第i個(gè)字符串則dp[i][j][k] = dp[i-1][j][k],取兩者大的作為dp[i][j][k]的值。由于dp[i][j][k]只與dp[i-1][*][*]相關(guān),所以這里可以重復(fù)使用???

m

?

n

個(gè)數(shù)據(jù)將空間復(fù)雜度降為?(???)

O

(

m

?

n

)

,只需在遍歷時(shí)從后向前遍歷即可。具體代碼:

class Solution:

def findMaxForm(self, strs: List[str], m: int, n: int) -> int:

dp = [[0 for j in range(n+1)] for i in range(m+1)]

for str in strs:

cnt0 = str.count('0')

cnt1 = str.count('1')

for i in reversed(range(cnt0,m+1)):

for j in reversed(range(cnt1,n+1)):

dp[i][j] = max(dp[i][j],dp[i-cnt0][j-cnt1]+1)

return dp[-1][-1]

[leetcode]518.Coin Change 2

建立dp數(shù)組,保存能到達(dá)當(dāng)前amount的步數(shù)。逐個(gè)金額遍歷,看只用前i個(gè)金額能到達(dá)j的步數(shù)有多少,實(shí)現(xiàn)方法是累加起來(lái)dp[當(dāng)前amount - 第i個(gè)金額],最后返回dp[amount]。

class Solution:

def change(self, amount: int, coins: List[int]) -> int:

dp = [0 for i in range(amount+1)]

dp[0] = 1

for coin in coins:

for i in range(coin,amount+1):

dp[i] += dp[i-coin]

return dp[-1]

總結(jié)

對(duì)于常見(jiàn)的動(dòng)態(tài)規(guī)劃問(wèn)題,有以下實(shí)例:

1.斐波那契數(shù)列(Climbing Stairs)

2.01背包問(wèn)題

3.最長(zhǎng)公共子序列

4.旅行商問(wèn)題 n!

Attention:

動(dòng)態(tài)規(guī)劃算法用到的題目存在很多套路

滾動(dòng)數(shù)組,狀態(tài)壓縮,(升維,單調(diào)性,四邊形不等式(高級(jí)套路))

本質(zhì):先暴力,找冗余,去冗余

動(dòng)態(tài) 枚舉 java,動(dòng)態(tài)規(guī)劃如何測(cè)試是否超時(shí)