暴力遞歸: 1,把問題轉化爲規模縮小了的同類問題的子問題 2,有明確的不需要繼續進行遞歸的條件(base case) 3,有當得到了子問題的結果之後的決策過程 4,不記錄每一個子問題的解
動態規劃: 1,從暴力遞歸中來 2,將每一個子問題的解記錄下來,避免重複計算 3,把暴力遞歸的過程,抽象成了狀態表達 4,並且存在化簡狀態表達,使其更加簡潔的可能
1、求n!的結果
想法是轉換成fact(n)=n*fact(n-1)
# 求n!,時間複雜度爲O(logN)
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
if __name__ == '__main__':
n = int(input())
print(factorial(n))
2、漢諾塔問題 打印n層漢諾塔從最左邊移動到最右邊的全部過程
A,B,C三個圓柱,分別爲初始位,過渡位,目標位,設A柱爲初始位,C位爲最終目標位
(1)將最上面的n-1個圓盤從初始位移動到過渡位
(2)將初始位的最底下的一個圓盤移動到目標位
(3)將過渡位的n-1個圓盤移動到目標位
對於遞歸算法中的嵌套函數f(n-1)來說,其初始位,過渡位,目標位發生了變化
# 漢諾塔問題
def Hanoi(n,from1,help,to):
if n == 1:
print(from1, '-->', to)
else:
Hanoi(n-1, from1, to, help)
print(from1, '-->', to)
Hanoi(n-1, help, from1, to)
if __name__ == '__main__':
n = int(input())
Hanoi(n,'A','B','C')
3、打印一個字符串的全部子序列,包括空字符串
每次都選擇是否將當前字符加入子序列
# 求字串的所有子序列
def printAllSubsquence(test, i, res):
if i == len(test):
print(res)
return
printAllSubsquence(test, i+1, res) # 當前位置字符不加入
printAllSubsquence(test, i+1, res + test[i]) # 當前位置字符加入
if __name__ == '__main__':
s = input()
res = ""
printAllSubsquence(s, 0, res)
4、打印一個字符串的全部排列
此題跟https://leetcode-cn.com/problems/permutations/是差不多的。故下面的代碼是數字的全排列,主要思路是先排好第一個,後面的n-1個數繼續全排列。
class Solution(object):
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
ans = []
if len(nums)==0 or len(nums)==1:
return [nums]
for index,item in enumerate(nums):
res = nums[:index] + nums[index+1:]
for j in self.permute(res):
ans.append(j+[item])
return ans
進階版:給定一個可包含重複數字的序列,返回所有不重複的全排列。題目地址:https://leetcode-cn.com/problems/permutations-ii/ 主要是,加入一個去重的判斷:
# 全排列,有重複數字
def permuteUnique(nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
ans = []
if len(nums) == 0 or len(nums) == 1:
return [nums]
for index, item in enumerate(nums):
res = nums[:index] + nums[index + 1:]
for j in permuteUnique(res):
ans.append(j + [item])
return ans
5、最小路徑和
給你一個二維數組,二維數組中的每個數都是正數,要求從左上 角走到右下角,每一步只能向右或者向下。沿途經過的數字要累 加起來。返回最小的路徑和。題目地址:https://leetcode-cn.com/problems/minimum-path-sum/
以這道題爲例子,演示怎麼從暴力遞歸改成動態規劃:
首先是暴力遞歸,從右下角開始,這個數只能往左或者往上走,查看每一步往右或者往上最小的,加起來。
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
return self.process1(grid,len(grid)-1,len(grid[0])-1)
def process1(self,grid,i,j):
res = grid[i][j]
if i==0 and j==0:
return res
if i==0 and j!=0:
return res + self.process1(grid,i,j-1)
if j==0 and i!=0:
return res + self.process1(grid,i-1,j)
return res + min(self.process1(grid,i-1,j), self.process1(grid,i,j-1))
但如果把這個答案放入leetcode中,很顯然會超出時間限制,因此要將暴力遞歸改成動態規劃:
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m,n = len(grid),len(grid[0])
dp = [[0 for _ in range(n)] for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1,m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1,n):
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1,m):
for j in range(1,n):
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]