栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

动态规划-python版本

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

动态规划-python版本

 1、斐波那契数列
# -*- coding:utf-8 -*-
def fib(n):
    """
    递归方式实现斐波那契数列---时间复杂度O(2^n),空间复杂度O(n)
    :param n:
    :return:
    """
    if n < 2:
        return n
    else:
        return fib(n-1) + fib(n-2)

def fib1(n):
    """
    dp方式实现斐波那契数列---时间复杂度O(n),空间复杂度O(1)
    :param n:
    :return:
    """
    if n < 2:
        return n
    dp = [0, 1]  # 初始化
    for i in range(2, n + 1):
        # dp.append(dp[i-1] + dp[i - 2])     # 空间复杂度高 O(n)
        dp[0], dp[1] = dp[1], dp[0] + dp[1]  # 空间复杂度 O(1)----滚动dp一维数组
    return dp[-1]
if __name__ == '__main__':
    print(fib(8))
    print(fib1(8))

变长的dp动画 

 滚动的dp动画

2、爬楼梯

题目:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。  

每次你可以爬 1 或 2 个台阶。你有多少种不同的⽅法可以爬到楼顶呢?

思路:

递推公式:dp[i] = dp[i-1] +dp[i-2]

dp[i]存储的是上第i阶台阶的方法总数:要不就是i-1阶爬一阶上来,要不就是i-2阶爬2个台阶上来

初始化:和斐波那契数列一波相比只是初始不一样,第一个台阶的方法1种,第二个台阶方法是2种

遍历顺序:遍历台阶,从第一阶向后

def fib1(n):
    """
    dp方式实现爬楼梯---时间复杂度O(n),空间复杂度O(1)
    :param n:
    :return:
    """
    if n < 2:
        return n
    dp = [1, 2]  # 初始化这里和斐波那契不一样
    for i in range(2, n + 1):
        # dp.append(dp[i-1] + dp[i - 2])     # 空间复杂度高 O(n)
        dp[0], dp[1] = dp[1], dp[0] + dp[1]  # 空间复杂度 O(1)----滚动dp一维数组
    return dp[1]
    return dp[-1]

3、使⽤最⼩花费爬楼梯

LeetCode地址:力扣

题目:数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。

每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。

请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

思路:

递推公式:dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]

到达第i个台阶所花费的最少体⼒为dp[i]

初始化:dp[0],dp[1] = cost[0],cost[1] 

              这里dp[1]并没有取min(cost[0],cost[1])因为这里如果去了cost[0],那就没有从

             cost[1]作为起点的情况就给排除了,最后的结果也是取最后两个值的最小值也体现

             了这个点

到达第i个台阶所花费的最少体⼒为dp[i]

遍历顺序:遍历台阶,即是遍历cost

def fun1(cost):
    dp = cost[0:2]
    for i in range(2, len(cost)):
        dp[0], dp[1] = dp[1], min(dp[0], dp[1]) + cost[i]

    return min(dp)

4、不同路径

LeetCode地址:https://leetcode-cn.com/problems/unique-paths/

题目:

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

思路:

递推公式:[i][j]=dp[i - 1][j] + dp[i][j - 1] 

走到[i][j]的网格的路径总数,是来自从上、从左来的路径总数

初始化:dp[m][n] = 都是1

遍历顺序:第一行和第一列只有一种路径,所以遍历行从1,列也从1

def uniquePathsWithObstacles(obstacleGrid):
    """二维dp数组解决不同路径问题"""
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    dp = [[1 for x in range(n)] for y in range(m)]

    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

    return dp[-1][-1]

def uniquePathsWithObstacles(obstacleGrid):
    """一维dp数组解决不同路径问题"""
    # 不同路径1中可以使用一维dp数组def uniquePaths(m, n):
    dp = [1 for x in range(n)]

    for i in range(1, m):
        for j in range(1, n):
            dp[j] += [j - 1]

    return dp[-1]

 5、不同路径 II

题⽬链接:https://leetcode-cn.com/problems/unique-paths-ii/

与不同路径的区别

(1)遇到障碍物,说明到达这个网格的路径总数为0

(2)遍历顺序:第一行如果有障碍物,从障碍物向后就不能走了

                           第一列如果有障碍物,从障碍物向下就不能走了

def uniquePathsWithObstacles(obstacleGrid):
    """二维dp数组解决不同路径问题"""
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        dp = [[1 for x in range(n)] for y in range(m)]

        for i in range(0, m):
            for j in range(0, n):
                if obstacleGrid[i][j] == 1:#遇到障碍物走到这个网格的路径就为0
                    dp[i][j] = 0
                elif i == 0 and j == 0:
                    pass
                elif i == 0 and j > 0:
                    dp[i][j] = dp[i][j - 1]
                elif j == 0 and i > 0:
                    dp[i][j] = dp[i - 1][j]
                else:
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

        return dp[-1][-1]

def uniquePathsWithObstacles(obstacleGrid):
    """一维dp数组解决不同路径问题"""
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        dp = [1 for x in range(n)]

        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    dp[j] //= 2   #遇到障碍物只是阻挡了一个方向所以除以2
                else:
                    dp[j] += dp[j - 1]

        return dp[-1]

6、整数拆分

 LeetCode地址343题: 力扣

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

思路:

递推公式:dp[i] = max(dp[i], dp[i-j]*j, (i-j)*j)

                 列举拆分所有的情况,dp[i]依赖dp[i-j]的写法

         比如:dp[3]拆分情况----

                       首先尝试拆出来1----dp[3]=0、1*2=2、1*dp[2]=1----dp[3]=2

                       再次尝试拆出来2----dp[3]=2、2*1=2、2*dp[1]=0----dp[3]=2

                   dp[4]拆分情况----

                        首先尝试拆出来1------dp[4]=0、1*3=3、1*dp[3]=2-----dp[4]=3

                        再次尝试拆出来2------dp[4]=3、2*2=4、2*dp[2]=2-----dp[4]=4

                        再次尝试拆出来3------dp[4]=4、3*1=3、3*dp[1]=0-----dp[4]=4                    

初始化:dp[2] = 1

               dp[0],dp[1]没有意义,0、1不能拆分,题目也说了从2开始

遍历顺序:遍历dp从3开始,应为2已经初始化了

                  拆分的场景就是从1到i的所有情况

#方法1:dp动态规划
    def integerBreak(n):
        dp = [0] * (n + 1)
        dp[2] = 1
        for i in range(3, n + 1):
            for j in range(1, i):
                dp[i] = max(dp[i],dp[i-j]*j, (i-j)*j)
        return dp[-1]

#方法2:贪心--3这个特殊的数么有啥理论依据
    def integerBreak(n):
        #贪心
        if n == 2:
            return 1
        if n == 3:
            return 2
        if n == 4:
            return 4
        result = 1
        while n > 4:
            result *= 3
            n -= 3
        result *= n
        return result

背包问题+背包回溯

6、01背包

学习视频:【动态规划】背包问题_哔哩哔哩_bilibili

学习文章:【动态规划】一次搞定三种背包问题 - 弗兰克的猫 - 博客园

题目:一个背包可以装M重的东西,物品5个,重量为[2, 2, 6, 5, 4],价值为[3, 6, 5, 4, 6], 问:如何装可以得到最大的价值?

def bag(bag_w, num, weights, values):
    """
    01背包问题
    :param bag_w: 背包容量
    :param num: 物品个数
    :param weights: 物品重量list
    :param values: 物品价值list
    :return: 描绘的二维数组
    """
    # 初始化一个二维数据,放入各种情况下最佳价值,右下角就是最优解
    dp = [[0 for row in range(bag_w + 1)] for column in range(num + 1)]

    for row in range(1, num + 1):
        for col in range(1, bag_w + 1):
            if weights[row - 1] > col:  # 此时背包容量col小于row物品的容量,放不下row物品
                dp[row][col] = dp[row - 1][col]
            else:
                # 找到去掉row物品容量col - weights[row - 1]对应的row-1个物品的最大价值
                t1 = dp[row - 1][col - weights[row - 1]] + values[row - 1]
                t2 = dp[row - 1][col]
                dp[row][col] = max(t1, t2)

    # 找到装入了那些物品----背包回溯
    # 从右下角开始找,判断第五个物品是否装进口袋看:装第五个物品的最优解是否大于装第四个物品的最优解,大说明装入了,不大就是没有装入
    good_value = dp[-1][-1]
    good_n = []

    while num > 0 and bag_w > 0:
        if dp[num][bag_w] > dp[num - 1][bag_w]:
            good_n.append(num)
            bag_w -= weights[num - 1]
        num -= 1

    print("背包最大价值:%s" % good_value)
    print("背包物品编号:%s" % good_n[::-1])


def bag1(bag_w, num, weights, values):
    # 01背包不做回溯的话,可以使用一维dp滚动数组
    # 初始化一个一维数据,放入不同背包容量时最大价值
    # 难点在循环顺序
    dp = [0 for row in range(bag_w + 1)]

    for row in range(0, num):
        for col in range(bag_w, weights[row]-1, -1):
            dp[col] = max(dp[col], dp[col - weights[row]] + values[row])

    return dp[-1]

if __name__ == '__main__':
    num = 5                       # 5个物品
    bag_w = 10                    # 背包总容量
    weights = [2, 2, 6, 5, 4]     # 物品重量集合
    values = [6, 3, 5, 4, 6]      # 物品价值集合
    bag(bag_w, num, weights, values)
    bag1(bag_w, num, weights, values)

二维图解

一维图解

(1)分割等和⼦集 

 LeetCode地址: 力扣

题目:给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

思路:将这些nums的元素当做物品,重量=价值=nums对应元素的值,背包假设为nums的和sum(nums)的一半

           转化为:背包容量sum(nums)/2,是否有恰好装满的情况(装满了,剩下的一半也是剩下物品的重量了)

递推公式:   dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])     (一维)   

                     dp[i][j] = max(dp[i-1][j], dp[i-1][j - nums[i]] + nums[i])     (二维) 

初始化:初始值都为0

遍历顺序:和上面的01背包一致

  def canPartition(self, nums: List[int]) -> bool:
        "01背包的一维数组解法"
        total = sum(nums)
        if total <= 1 or total % 2 != 0:
            return False
        target = total // 2
        dp = [0] * (target + 1)

        for i in range(len(nums)):
            for j in range(target, nums[i] - 1, -1):
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

        return dp[-1] == target

def canPartition1(self, nums: List[int]) -> bool:
        "01背包的二维数组解法"
        total = sum(nums)
        if total <= 1 or total % 2 != 0:
            return False
        target = total // 2
        dp = [[0] * (target + 1)] * (len(nums)+1)

        for i in range(len(nums)+1):
            for j in range(nums[i], target+1):
                dp[i][j] = max(dp[i-1][j], dp[i-1][j - nums[i]] + nums[i])

        return dp[-1][-1] == target

(2)最后⼀块⽯头的重量 II

 LeetCode地址: 力扣

题目:

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

思路:可以转化为分割等和⼦集的问题,target=total//2

          不同点:(1)total不能等分的特殊情况不用处理

                      (2)子集问题中dp[-1]==target,

                        这里(total -dp[-1]) -dp[-1],含义将total分2份,一份是dp[-1],一份是total-dp[-1],他们差就是击碎后的最小值了,不理解我们在分析下,(total -dp[-1]) -dp[-1]  === 2(target -dp[-1]),这个式子是不是target和dp[-1]的值越相近那结果是不是越小?dp[-1]如果等于target就是等分子集了,是不是就是完美粉碎了?如果还不理解dp[-1]的意义,那就在讲,转化01背包问题:target是一个背包,装石头或者上面等分子集的nums的元素,石头重量即是价值,这样取如何装找到最大价值

递推公式:   dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])     (一维)   

                     dp[i][j] = max(dp[i-1][j], dp[i-1][j - nums[i-1]] + nums[i-1])     (二维) 

初始化:初始值都为0

遍历顺序:和上面的01背包一致

def lastStoneWeightII(self, stones: List[int]) -> int:
        """一维数组dp解法"""
        total = sum(stones)
        target = total // 2
        dp = [0] * (target + 1)

        for i in range(len(stones)):
            for j in range(target, stones[i] - 1, -1):
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])

        return total- dp[-1] - dp[-1]


    def lastStoneWeightII(self, stones: List[int]) -> int:
        """二维数组dp解法"""
        total = sum(stones)
        target = total // 2
        dp = [[0 for x in  range(target + 1)] for y in range(len(stones)+1)]

        for i in range(1, len(stones)+1):
            for j in range(1, target+1):
                if j < stones[i-1]:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j - stones[i-1]] + stones[i-1])

        return total- dp[-1][-1] - dp[-1][-1]

(3)目标和

 LeetCode地址: 力扣

题目:

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

思路:每个元素有正负的两种情况,是否也是和分割等和⼦集、最后⼀块⽯头的重量 II问题类似,都是将nums分成两份,一份装,一份不装入,装入的是正号,不装入的是负号,是不是也像粉碎石头了?

          不同点:这个背包的容量是多少呢?分隔等和子集的背包容量是total的一半,这里呢?一份正数和、一份负数和

                 正数和 + 负数和 = total

                 正数和 -  负数和 = target

                 正数和 = (total + target)/ 2

所以就转化为了正数和的表达式 的数目

递推公式:   dp[j] = dp[j]+dp[j - nums[i]]   (一维)   

                     dp[i][j] = dp[i-1][j]+dp[i-1][j - nums[i-1]] (二维) 

初始化:dp[0] =1、dp[i][0]=0 这个是二维的情况

遍历顺序:和上面的01背包一致

def findTargetSumWays(self, nums: List[int], target: int) -> int:
        # 二维数组dp解法
        total = sum(nums)
        if target > total or (target + total) % 2 != 0 or target < -total:
            # 特殊情况处理:target不能大于total,小于-total,如果功能不能整除2,就得不到正数和的部分
            return 0
        bag_size = (target + total) // 2
        dp = [[0 for x in range(bag_size + 1)] for y in range(len(nums) + 1)]
        #初始化
        for z in range(len(nums)+1):
            dp[z][0] = 1

        for i in range(1, len(nums)+1):
            for j in range(0, bag_size+1): # 这个变量背包从0开始,这里的0有具体的含义了,之前价值0是无意义的了
                if j < nums[i-1]:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j - nums[i-1]] + dp[i-1][j]

        return dp[-1][-1]


def findTargetSumWays(self, nums: List[int], target: int) -> int:
        # 一维数组dp解法
        total = sum(nums)
        if target > total or (target + total) % 2 != 0 or target < -total:
            # 特殊情况处理:target不能大于total,小于-total,如果功能不能整除2,就得不到正数和的部分
            return 0

        bag_size = (target + total) // 2
        dp = [0] * (bag_size + 1)
        dp[0] = 1
        for i in range(len(nums)):
            for j in range(bag_size, nums[i] - 1, -1):
                dp[j] = dp[j] + dp[j - nums[i]]

        return dp[-1]

​(4)一和零

题目链接:474. 一和零

题目:

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

思路:

dp[i][j]的含义:i个0和j个1组成的最大子集长度

递推公式:   dp[i][j] = max(dp[i][j], dp[i - m_sum][j - n_sum] + 1) 

初始化:0,组成0个0和0个1的字符串是0个

遍历顺序:和上面的01背包一致

题目特点:这里的物品是每一个字符串,背包是m个0和n个1的背包,这个背包不是之前只考虑物品的重量属性了,需要考虑物品的两个属性才能装入这个背包。动图效果多理解下

    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp = [[0 for x in range(n + 1)] for y in range(m + 1)]

        for w in strs:
            m_sum = w.count("0")
            n_sum = w.count("1")
            for i in range(m, m_sum - 1, -1):
                for j in range(n, n_sum - 1, -1):
                    dp[i][j] = max(dp[i][j], dp[i - m_sum][j - n_sum] + 1)

        return dp[-1][-1]

类似面试题目(360的题):

有彩色粉笔m个,白色粉笔n个。
a个彩色粉笔+b个白色粉笔可以打包卖x元;
c个彩色粉笔打包卖y元;
d个白色粉笔打包卖z元。
问粉笔不一定用完,得到的最多价钱是多少?

举例

输入: 
m=5, n=5, a=1, b=2, c=3, d=3, x=2, y=1, z=3, 
输出: 
6

# 第一种:dp动态规划
def chalk1(m, n, a, b, c, d, x, y, z):
    # dp[i][j]表示i个彩色粉笔和j个白色粉笔的最大价值
    dp = [[0 for _ in range(m + 1)] for _ in range(n + 1)]
    # 物品做一个数据结构,方便下面遍历物品
    temp = {(a, b): x, (c, 0): y, (0, d): z}
    for temp_m, temp_n in temp:
        for i in range(m, temp_m - 1, -1):
            for j in range(n, temp_n - 1, -1):
                # 递推公式和零和一的题目一模一样,寻找不装temp这个物品的最大价值+temp物品的价值
                dp[i][j] = max(dp[i][j], dp[i - temp_m][j - temp_n] + temp[(temp_m, temp_n)])
    return dp[-1][-1]


#第二种:递归
def chalk2(m, n, a, b, c, d, x, y, z):
    res={} #这里放所有购买的彩色和白色粉笔的总数组合

    def temp(m, n):  # 此方法就是寻找m,n的不同组合的价值是多少
        if (m, n) not in res:
            r = 0
            # 找到三种购买方式的最大价值
            if m >= a and n >= b: 
                r = max(r, temp(m - a, n - b) + x)
            if m >= c:
                r = max(r, temp(m - c, n) + y)
            if n >= d:
                r = max(r,temp(m, n - d) + z)
            res[(m, n)] = r
        return res[(m, n)]

    return temp(m, n)

#第三种:递归,相比较第二种,没有创建递归子函数
def chalk3(m, n, a, b, c, d, x, y, z):
    res={}
    if (m, n) not in res:
        r = 0
        if m >= a and n >= b:
            r = max(r, chalk3(m - a, n - b, a, b, c, d, x, y, z) + x)
        if m >= c:
            r = max(r, chalk3(m - c, n, a, b, c, d, x, y, z) + y)
        if n >= d:
            r = max(r, chalk3(m, n - d, a, b, c, d, x, y, z) + z)
        res[(m, n)] = r

    return res[(m, n)]            

 

7、完全背包

(1)零钱兑换II

(2)377题:组合总和IV

(3)322题:零钱兑换

(4)279题:完全平方数

(5)139题:单词拆分 

8、多重背包 (1)打家劫舍 (2)打家劫舍II (3)打家劫舍III (4)买卖股票的最佳时机 (5)买卖股票的最佳时机II (6)买卖股票的最佳时机III (7)买卖股票的最佳时机IV (8)最佳买卖股票时机含冷冻期 (9)买卖股票的最佳时机含⼿续费 (10)最⻓递增⼦序列 (11)最⻓连续递增序列 (12)最⻓重复⼦数组

3. 无重复字符的最长子串有点反义的题目3. 无重复字符的最长子串

    def lengthOfLongestSubstring(self, s: str) -> int:
        n, max_n, temp = 0, 0, ""

        for i in s:
            if i not in temp:
                temp += i
                n += 1
            else:
                if n > max_n:
                    max_n = n
                pos = temp.index(i)
                temp = temp[pos+1:] + i
                n = len(temp)

        if n > max_n:
            max_n = n
        return max_n
(13)最⻓公共⼦序列 (14)不相交的线 (15)最⼤⼦序和 (16)判断⼦序列 (17)不同的⼦序列 (18)两个字符串的删除操作 (19)编辑距离 (20)回⽂⼦串 (21)最⻓回⽂⼦序列

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/324391.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号