暴力递归: 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]