题目一: 机器人走无障碍物的方格的不同路径个数
解题思路
典型的递归: f(m,n) = f(m-1,n) + f(m,n-1)
,到达当前位置的所有路径可以来自上面一格的down,也可以是左边一格的right。
# 开辟一个dp内存,空间复杂度o(m*n)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[1]*n for i in range(m)]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i][j-1] + dp[i-1][j]
return dp[-1][-1]
# 只用一行的dp,滚动数组,节省内存,空间复杂度o(min(m,n))
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m < n: m, n = n, m # 选择空间复杂度为min(m,n),
dp = [1]*n
for i in range(1,m):
for j in range(1,n):
dp[j] = dp[j]+dp[j-1]
return dp[-1]
扩展
如果题目不仅仅要求路径个数,还要求返回所有路径。就不能用动态规划,只能递归求解
# 时间复杂度为2^m
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m <= 0 or n <= 0:
return []
if m == 1 and n == 1:
return [[]]
up_path = self.uniquePaths(m-1, n)
left_path = self.uniquePaths(m, n-1)
respath = []
for path in up_path:
respath.append(path+['down'])
for path in left_path:
respath.append(path+['right'])
return respath
# 用dp保存已经得到的path,空间换时间
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dppath = [[[]]*n for _ in range(m)]
def findPath(m, n):
if m < 0 or n < 0:
return []
if m == 0 and n == 0:
return [[]]
if dppath[m][n]: return dppath[m][n]
up_path = findPath(m-1, n)
left_path = findPath(m, n-1)
respath = []
for path in up_path:
respath.append(path+['down'])
for path in left_path:
respath.append(path+['right'])
dppath[m][n] = respath
return respath
return findPath(m-1 ,n-1)
题目二: 机器人走有障碍物的方格的不同路径个数
# dp二维数组,空间复杂度为o(m*n)
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
row, col = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0]*col for _ in range(row)]
i = 0
while i < col and obstacleGrid[0][i] == 0: # 初始化第一行,一旦有障碍物后面都为0
dp[0][i] = 1
i += 1
i = 0
while i < row and obstacleGrid[i][0] == 0: # 初始化第一列,一旦有障碍物后面都为0
dp[i][0] = 1
i += 1
for i in range(1, row):
for j in range(1, col):
if not obstacleGrid[i][j]:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
# 滚动数组, 空间复杂度为o(m),没有办法o(min(m,n))了
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
row, col = len(obstacleGrid), len(obstacleGrid[0])
i = 0
dp = [0]*col
while i < col and obstacleGrid[0][i] == 0:
dp[i] = 1
i += 1
for i in range(1, row):
dp[0] = int(not obstacleGrid[i][0] and dp[0]) #初始化每行的第一列元素
for j in range(1, col):
if obstacleGrid[i][j]:
dp[j] = 0
continue
dp[j] = dp[j-1] + dp[j]
return dp[-1]
扩展
如果题目不仅仅要求路径个数,还要求返回所有路径。就不能用动态规划,同样也是递归求解。
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
def findPath(m, n):
if m < 0 or n < 0 or obstacleGrid[m][n]==1:
return []
if m == 0 and n == 0:
return [[]]
up_path = findPath(m-1, n)
left_path = findPath(m, n-1)
respath = []
for path in up_path:
respath.append(path+['down'])
for path in left_path:
respath.append(path+['right'])
return respath
return findPath(m-1 ,n-1)
# 用dppath 保存已经得到的path,空间换时间
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dppath = [[[0]]*n for _ in range(m)] # 为了区分已遍历但因为有障碍物而无路径[]和未遍历的情况。设置[0]代表未遍历。
def findPath(m, n):
if m < 0 or n < 0:
return []
if obstacleGrid[m][n]==1:
dppath[m][n] = []
return []
if m == 0 and n == 0:
dppath[m][n] = [[]]
return [[]]
if dppath[m][n] != [0]: return dppath[m][n] #说明已经遍历过
up_path = findPath(m-1, n)
left_path = findPath(m, n-1)
respath = []
for path in up_path:
respath.append(path+['down'])
for path in left_path:
respath.append(path+['right'])
dppath[m][n] = respath
return respath
return findPath(m-1 ,n-1)
题目三: 青蛙跳石头
解题思路
首先分析最后一步是从前面某块石头跳过来,然后分析能跳过来的条件,如下:
转移方程:
三种写法:
其中第一种写法的思路是:若可到达当前石头,则计算从当前石头可跳到的下一块所有石头。
class Solution:
def canCross(self, stones: List[int]) -> bool:
# 能够跳到f(i)且i与n-1的距离小于ai
# 第一种写法:前向dp+前向散播
dp = [False]*len(stones)
dp[0] = True
for i in range(len(stones)):
if not dp[i]: continue
for j in range(1, min(stones[i]+1, len(stones)-i)):
dp[i+j] = True
return dp[-1]
# 第二种写法:前向dp+逆向判断
# 时间复杂度o(n^2),空间复杂度o(n)
# dp = [False] * len(stones)
# dp[0] = True
# for i in range(1, len(stones)):
# for j in range(i):
# if dp[j] and stones[j] >= i-j:
# dp[i] = True
# break
# return dp[-1]
# 第三种写法:逆向递归+逆向判断
# def dump(n):
# if n == 0: return True
# for i in range(n):
# if stones[i] >= n - i and dump(i):
# return True
# return False
# return dump(len(stones)-1)
题目四:排列组合问题
dp为三维, dp[i][j][k]含义为到数组中的第i个元素为止使用了j个元素使得元素和为k的组合个数。其实dp的思路很简单,精髓就是枚举出所有的情况为转移做准备,所以这里用三维dp枚举所有可能。
## 把正整数均分为两组A和B,使得A之和大于B总共有几组
## dp做法
def countArrange(nums):
n = len(nums)
if n%2: return 0
sumn = sum(nums)
target = sum(nums)//2 + 1
dp = [[[0]*(sumn+1) for _ in range(n//2+1)] for _ in range(n+1)]
for i in range(n+1):
dp[i][0][0] = 1
for i in range(1, n+1):
for j in range(1, n//2+1):
for k in range(sumn+1):
dp[i][j][k] = dp[i-1][j][k] ## 不选
if k-nums[i-1]>=0:
dp[i][j][k] += dp[i-1][j-1][k-nums[i-1]] ## 选
return sum(dp[n][n//2][target:])
nums = list(range(10))
# nums = [0,5,2,4,3,1]
countArrange(nums)
滚动数组优化空间版本
## dp滚动数组做法,j从后往前滚, k无所谓
def countArrange(nums):
n = len(nums)
if n%2: return 0
sumn = sum(nums)
target = sum(nums)//2 + 1
dp = [[0]*(sumn+1) for _ in range(n//2+1)]
dp[0][0] = 1
for i in range(1, n+1):
for j in range(n//2, 0, -1):
for k in range(sumn, -1, -1):
if k-nums[i-1]>=0:
dp[j][k] += dp[j-1][k-nums[i-1]] ## 选
return sum(dp[n//2][target:])
nums = list(range(10))
# nums = [0,5,2,4,3,1]
countArrange(nums)
补一个回溯写法,虽然很可能超时
## 回溯做法
def countArrange(nums):
lenth = len(nums)
target = sum(nums)//2 + 1
global ans
ans = 0
def traceback(st, n, target):
if n == 0:
if target <= 0:
global ans
ans += 1
return
if st >= lenth:
return
for i in range(st,lenth):
traceback(i+1, n-1, target-nums[i])
traceback(0, lenth//2, target)
return ans
nums = list(range(10))
countArrange(nums)