近兩天在刷leetcode自然也接觸到了程式設計師必備的基礎演算法,動態規劃問題。在網上找了些教程,摘抄了些,權當是自己日後回看的筆記把。
大致可以理解為尋優過程中將問題轉化為乙個迭代方程並構建邊界條件。轉化條件如下:
1.問題中的狀態滿足最優性原理。
2.問題中的狀態必須滿足無後效性。
並轉化之後實現方法也有兩種,其一為常用的遞迴呼叫,二為迴圈遞推。網上找的一些題目如下:
1.割繩子
"""
剪繩子問題
給你一根長度為n的繩子,請把繩子剪成m段(m,n都是整數),每段繩子的
長度記為k[0],k[1],k[2]…. 請問如何剪繩子使得k[0],k[1],k[2]
"""def cut_rope(n):
""":param n:繩子長
:return: 最大面積
"""if n < 2:
return 0
if n == 2:
return 1
if n == 3:
return 2
h = [0] * 50
h[0] = 0
h[1] = 1
h[2] = 2
h[3] = 3
for i in range(4, n+1):
max_ = 0
for j in range(1,i//2+1):
mult = h[j] * h[i-j]
max_ = max(max_, mult)
h[i] = max_
return h[n]
if __name__ == '__main__':
n = 5
print(cut_rope(n))
2. 爬樓梯
"""
乙個人每次只能走一層樓梯或者兩層樓梯,問走到第80層樓梯一共有多少種方法。
設dp[i]為走到第i層一共有多少種方法,那麼dp[80]即為所求。很顯然dp[1]=1, dp[2]=2
(走到第一層只有一種方法:就是走一層樓梯;走到第二層有兩種方法:走兩次一層樓梯或者走一次兩層樓梯)。
同理,走到第i層樓梯,可以從i-1層走一層,或者從i-2走兩層。很容易得到:
遞推公式:dp[i]=dp[i-1]+dp[i-2]
邊界條件:dp[1]=1 dp[2]=2
"""def climb_ladder_top_bottom(n):
if n <= 0:
return 0
if n == 1:
return 1
if n == 2:
return 2
return climb_ladder_top_bottom(n-1) + climb_ladder_top_bottom(n-2)
def climb_ladder_bottom_top(n):
if n <= 0:
return 0
if n == 1:
return 1
if n == 2:
return 2
dp = [0] * 50
dp[0] = 0
dp[1] = 1
dp[2] = 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
if __name__ == '__main__':
n = 5
print(climb_ladder_top_bottom(n))
print(climb_ladder_bottom_top(n))
3.最少硬幣數量(找零錢)
"""
硬幣問題
我們有面值為1元3元5元的硬幣若干枚,如何用最少的硬幣湊夠11元?
分析:1 求問題的最優解:最小的硬幣數
的最小硬幣數為 f(n-vi) 它的狀態變更不是按元數的,是按照上次拿的硬幣錢目
3 狀態轉移方程為 f(n)= min(f(n-vi)+1)
4 邊界問題(找到最後乙個重複的問題) 這裡
f(1)=1 ,f(2)=f(1)+f(1)=2 f(3)=min(1,f(2)+1)
f(4)=f(3)+1 f(5)=1
5 從上往下分析問題,從下往上解決問題。
"""
def less_coin_num(n):
# recursive
# boundary
if n <= 0:
return 0
if n == 1:
return 1
if n == 2:
return 2
if n == 3:
return 1
if n == 4:
return 2
if n == 5:
return 1
h = [1,3,5]
minx = n
for i in range(3):
count = less_coin_num(n-h[i]) + 1
minx = min(minx, count)
return minx
def less_coin_num2(n):
# loop, bottom-up
if n <= 0:
return 0
if n == 1:
return 1
if n == 2:
return 2
if n == 3:
return 1
if n == 4:
return 2
if n == 5:
return 1
h = [1,3,5]
for x in range(6, n+1):
minx = n
for i in range(3):
count = less_coin_num2(x-h[i])+1
minx = min(minx, count)
return minx
if __name__ == '__main__':
n = 11
print(less_coin_num(n))
print(less_coin_num2(n))
4.最長上公升子串行
"""
對於序列:4 1 2 4,它的最長上公升子串行是1 2 4,長度為3。
對於序列:4 2 4 5 6,它的最長上公升子串行是2 4 5 6,長度為4。
設a[i]表示原序列,設dp[i]表示以第i個數結尾的最長上公升序列的長度,那麼很顯然想匯出dp[i]的值,需要在dp[k](1<=k5.最長公共子串行
"""給定兩個序列x和y,稱序列z是x和y的公共子串行如果z既是x的乙個子串行,又是y的乙個子串行。例如,如果x= y=
那麼序列就是x和y的乙個公共子串行,但是它並不是x和y的最長公共子串行,因為它的長度為3。而同為x和y公共子串行的,長度為4,
因為找不到長度為5或更大的公共子串行,所以x和y的最長公共子串行長度就為4。
假設兩個序列陣列分別為a,b。定義f(i,j)為計算到a陣列第i個數、b陣列第j個數時所得到的最長公共子串行的長度。
這時有兩種情況:
1.假如a[i]=b[j],那麼f(i,j)=f(i-1,j-1)+1
2.假如a[i]!=b[j],那麼f(i,j)=max(f(i-1,j),f(i,j-1))
邊界條件為:f(i,0)=0 1<=i<=len(a)
f(0,j)=0 1<=j<=len(b)
演算法複雜度:o(n^2),len(a)表示陣列a的長度。
"""
def longest_shared_sub_sequence(nums1, nums2):
m = len(nums1)
n = len(nums2)
if m == 0 or n == 0:
return 0
nums1.insert(0,0)
nums2.insert(0,0)
dp = [[0]*(n+1)]*(m+1)
for i in range(1, m+1):
for j in range(1, n+1):
if nums1[i] == nums2[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return max(max(dp))
if __name__ == '__main__':
nums1 = [1,2,3,2,4,1,2]
nums2 = [2,4,3,1,2,1]
print(longest_shared_sub_sequence(nums1, nums2))
關於最長遞增子串行的實際應用 動態規劃
a.b.1.對 中問題6 最長遞增子串行的改進,減少時間複雜度 演算法的思想 它不是使用的動態規劃法,而是普通的演算法思想,就是在陣列中直接儲存最長遞增子串行,在迴圈的過程中不斷的查詢插入位置,直到最終找到。下面列出實現的過程 從上面的過程可以看出該演算法的實現過程,在查詢sub合適插入位置的時候,...
組合問題與動態規劃的聯絡之應用
一,問題描述 假設有個機械人坐在 x y 網格的最左上角,每次只能向下或者向左移動。最左上角的座標標記為 0,0 最右下角的座標為 x,y 請問 機械人從 0,0 走到 x,y 共有多少種走法?其實這個問題與 這篇文章 中提到的問題非常相似。二,問題分析 這個問題一共有三種方式來求解。第一種是使用公...
動態規劃與序列問題
1.最長公共子串問題描述 如果字串一的所有字元按其在字串中的順序出現在另外乙個字串二中,則字串一稱之為字串二的子串。注意,並不要求子串 字串一 的字元必須連續出現在字串二中。動態規劃 使用dp i j 表示 以x i 和y j 結尾的最長公共子串的長度,因為要求子串連續,所以對於x i 與y j 來...