0
点赞
收藏
分享

微信扫一扫

动态规划|0-1背包 与 编辑距离| 思路与python题解

西街小学的王 2022-03-24 阅读 52

前言:

在前面的几个题中我们都用到了动态规划的思想,尤其是打家劫舍三问题都有涉及,而这两个题可以说是经典的动态规划问题了,在此记录一下。

题目:0-1背包:01背包_牛客题霸_牛客网 (nowcoder.com)

描述

已知一个背包最多能容纳体积之和为v的物品

现有 n 个物品,第 i 个物品的体积为 vi , 重量为 wi

求当前背包最多能装多大重量的物品?

示例  :4个物品 体积分别为 2 3 4 5       背包容积为 8
                        重量分别为 3 4 5 6

 答案:10  取体积为 3 和 5 的。

解题思路: 在此给出几个变量的意思:v【i】第i件物品的体积、m【i】第i件物品的质量、一个二维数组dp,dp【i】【j】 为前i件物品,在容积为j的背包中所能得到的最大质量。

        假设 我们知道前 i 件物品在容量为 j 的背包中能取得的最大的质量为 dp[i][j] 。那么当我们想要求取前 i+1 件物品在容量为 j 时取得的最大的质量应该怎么求呢?

        一 、首先我们要判断第i+1件物品所消耗的体积,如果 v[ i + 1] > j 那么 第 i+1 件物品就放不进容积为 j 的背包,此时 dp【i+1】【j】=dp【i】【j】。

        二、如果v【i+1】< j,那么第 i+1 件物品就能放进去,此时我们要考虑是否放。就是比较放与不放哪种情况下得到的质量最大。(1)如果放第 i+1 件物品,那么我们首先要留给他相应的体积,这时背包剩下的体积则为 j - v[ i+1],背包里的质量为 m[i+1] + dp[ i ][ j - v [ i+1 ] ] 。(2)如果不放则此时背包里的质量为 dp[ i ][ j ]。 比较 放 与 不放 取大的值存入dp[ i+1 ][ j ]。

        由此分析我们可以看出如果我们知道dp[ i ][ j ]  (0<= j <= 背包容积) 我们就能得到dp[ i+1 ][ j ] (0<= j <= 背包容积)于是我们使用双循环便能求出所有物品在给的背包下的最大的质量了。

        容易知道dp表前有几个初始值

前几个物品 | 背包容量012345678
0000000000
10
20
30
40

代码:

j = 8
v = [0, 2, 3, 4, 5]
m = [0, 3, 4, 5, 6]
dp = [[0 for _ in range(j + 1)] for _ in range(len(v))]

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

print(dp[len(v)-1][j])

        代码比较简单,但是思路还是比较难的。。对于特定输入的可以根据题目要求修改一下输入即可。 dp二维优化到一维,还能凹,但是我还没想清楚。

题目:编辑距离:编辑距离(一)_牛客题霸_牛客网 (nowcoder.com)

给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。

你可以对字符串进行3种操作:

        1.插入一个字符

        2.删除一个字符

        3.修改一个字符。

字符串长度满足 1 \le n \le 1000 \1≤n≤1000  ,保证字符串中只出现小写英文字母。

示例

输入:"nowcoder","new"

返回值:6

说明:"nowcoder"=>"newcoder"(将'o'替换为'e'),修改操作1次 "nowcoder"=>"new"(删除"coder"),删除操作5次

闲言碎语:

        这个题目估计从第一次见到今天(22.3.23)得有20来天了,期间断断续续的网上搜索过看过分析过,但是自己还是不太懂哈哈哈哈。我有没有可能是个笨蛋?hhhh还是自己没有专门看的原因吧。自己看的一些帖子的这个算法分析我都理解不到点。这次必定好好理解。

题目分析:

        首先考虑最简单的情况,如果是要将A变为B就是依此的修改,缺少就插入,多余就删去。这样的情况最多就需要max(A,B)次。哎呀实在不会就直接返回这个东西吧,估计能对几个 哈哈哈哈(摆烂王是我没错了)。

解题思路:

        让我们好好分析一下。假设有两个字符串(都是小写):a=“colorful”,b=“pour”。

        由于是要由str1->str2 因此我们规定我们的操作都是对str1执行的。

        首先机器要判断字符串是否相同是肯定不能看他一眼的,只能一个一个字符的比较。所以我们首先需要两个指针(i,j)指向两个字符串的末尾,然后向前遍历比较。

        NOTICE:为什么不指向开头?因为如果是从左向右遍历的话,对插入、删除操作来说会改变字符串的长度。最后不方便控制结束的条件。

        比较的情况只有两种,一种是 a[ i ] == b[ j ],这时我们只需要比较下一个两个字符串的下个字符 a[ i-1 ] 和 b [ j-1 ] 。

        另一种是 a[ i ] !=b[ j ] 。这时有三种操作:

                操作一:在 a[ i ] 的后面增加一个和 b[ j ] 相同的字符,注意这时我们要比较的是 a[ i ] 和

b [ j-1 ] 。因为 a[ i ] 后面添加了一个和 b[ j ] 相同的字符了。

                操作二:将 a[ i ] 删掉。这时我们比较的就是 a[ i-1 ] 和 b [ j ]。

                操作三:将 a[ i ] 改成b[ j ]。 这时我们比较的就是 a[ i-1 ] 和 b[ j-1 ]  。

         i ,j 的范围分别是[ 0, len(a)-1 ],[ 0 , len(b)-1 ]。如果我们的某个指针遍历到 -1 时,对 a 我们就只能添加(当 i 指向了-1 )或减少( 当 j 指向了-1 )。

                首先我们应该可以写出一个暴力的解法。如下

class Solution:
    def editDistance(self , str1: str, str2: str) -> int:
        # write code here
        
        la = len(str1)-1
        lb = len(str2)-1

        def pd(i, j):  # 判断 i,j 时如何操作
            if i == -1:  # 值为-1时添加j+1个
                return j+1
            if j == -1:
                return i+1
            if str1[i] == str2[j]:
                return pd(i - 1, j - 1)
            else:
                return min(pd(i, j - 1)+1, pd(i - 1, j)+1, pd(i - 1, j - 1)+1)


        return pd(la, lb)

        这个方法可以50%的测试。但是因为有太多重复的计算后面的测试点都会超时。我们添加一个记忆性的结构将我们算过的值存起来,那么重复的计算就会大大减少。

class Solution:
    def editDistance(self , str1: str, str2: str) -> int:
        # write code here
        
        la = len(str1)-1
        lb = len(str2)-1
        memer = dict()

        def pd(i, j):  # 判断 i,j 时如何操作
            if (i, j) in memer:
                return memer[(i, j)]
            if i == -1:  # 值为-1时添加j+1个
                memer[(i, j)] = j + 1
                return j + 1
            if j == -1:
                memer[(i, j)] = i + 1
                return i + 1
            if str1[i] == str2[j]:
                memer[(i,j)] = pd(i-1,j-1)
                return memer[(i,j)]
            else:
                memer[(i,j)] = min(pd(i, j - 1) + 1, pd(i - 1, j) + 1, pd(i - 1, j - 1) + 1)
                return memer[(i,j)]

        return pd(la, lb)

        我用了memer存储我们算出来的值。结果并不行。

        绝了,他的第六个样例太长了我们用这个递归的话直接给最大深度给干碎。绝了,我在我电脑上就保存,还以为是超时呢我,结果还有这个问题。报错如下

                RecursionError: maximum recursion depth exceeded in comparison 

        recursion (n. 递归)     comparsion (n.比较)exceed(v.超过)我在csdn学英语hh(bushi)

        既然这个方法我们不能得到全部的分数,那我们就放弃吧。哈哈哈哈老摆烂人了。

        当然是不行的。我们仔细的看看我们这个递归的思路。是不是他总是要将 i * j 个数的值求出来,才能堆之后的值。为什么不做一个二维的数组来存储呢?

        dp【i】【j】 表示 str1 前 i 个字符串 变为 str2 前 j 个字符串的最小编辑距离。好,再挖一个坑好吧。 都凌晨一点半了,我想躺床上去看看B站。今早起来写,起来写,希望我不会猝死

       

举报

相关推荐

0 条评论