动态规划专题【持续更新】

题目一: 机器人走无障碍物的方格的不同路径个数

leetcode62. 不同路径

在这里插入图片描述

解题思路

典型的递归: 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)

题目二: 机器人走有障碍物的方格的不同路径个数

leetcode63. 不同路径 II
在这里插入图片描述

# 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) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值