map的key可以试一个数组吗?_二维数组的 DP

寻找不同路径和

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

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

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

f7882b9f9c862993946ecdfea9d35af2.png

例如,上图是一个7 x 3 的网格。有多少可能的路径?

动态规划5大步骤:

1.定义状态:

即定义数据元素的含义,这里定义dp[i][j]为当前位置的路径数,i表示i列,j表示j行。注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 右下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要找的答案。

2.建立状态转移方程

因为从题目要求中可以看出,机器人只能向右或向下移动。

所以到达dp[i][j]就可能是经过dp[i-1][j]到达,也可能是经过dp[i][j-1]到达。

所以状态转移方程为:dp[i][j]=dp[i-1][j]+dp[i][j-1]

3.设定初始值:

通过状态转移方程可以看出,i和j下表要从1开始,否则会导致数组溢出异常。因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了。同时每一个位置点代表到达当前位置的路径条数,所以要设置最初的路径条数即第一行,第一列值为1。

dp[i][0]=1(相当于最上面一行,机器人只能一直往右走),

dp[0][j]=1,(相当于最左面一列,机器人只能一直往下走)

4.状态压缩:

上面时间复杂度:

,空间复杂度:
,优化目标

即优化数组空间,每次状态的更新只依赖于前一次的状态,优化一般作用于多维数组,观察是否可以将多维数组以一维数组来动态表示,即用一维数组来保存上次的状态,动态计算并替换当前位置下的路径数最新值。这道题的优化方法是存在的。

01bc0881b32e0e427c35c6c7dd59da47.png

当我们要填充第三行的值的时候,我们需要用到第一行的值吗?根据公式 dp[i][j] = dp[i-1][j] + dp[i][j-1],我们可以知道,当我们要计算第 j 行的值时,只用到第 j - 1 行。我们只需要用一个一维的 dp[] 来保存一行的历史记录就可以了。

1、刚开始初始化第一行,此时 dp[0..m-1] 的值就是第一行的值。

2、接着我们来一边填充第二行的值一边更新 dp[i] 的值,一边把第一行的值抛弃掉。

(1)、显然,矩阵(1, 0) 的值相当于以往的初始化值,为 1。然后这个时候矩阵 (0,0)的值不在需要保存了,因为再也用不到了。这个时候,我们也要跟着更新 dp[0] 的值了,刚开始 dp[0] = (0, 0),现在更新为 dp[0] = (1, 0)。

(2)、接着继续更新 (1, 1) 的值,根据之前的公式 dp[i][j] = dp[i-1][j] + dp[i][j-1]。即 (1,1)=(0,1)+(1,0)=2。

fde478599d9f186704469023c3837a45.png

以往的二维的时候, dp[i][j] = dp[i-1] [j]+ dp[i][j-1]。现在转化成一维,不就是 dp[i] = dp[i] + dp[i-1]

即 dp[1] = dp[1] + dp[0],而且还动态帮我们更新了 dp[1] 的值。因为刚开始 dp[i] 的保存第一行的值的,现在更新为保存第二行的值。

状态转移方程:dp[i] = dp[i-1] + dp[i].

初始值: f = [1]*m,取横轴

5.选出结果:

根据状态转移方程,求路径的总数,因此 dp[m-1] [n-1]表示的是到达最后位置的路径总条数。

def uniquePaths( m, n):
    f = [[0]*n for zong in range(m)]
    for i in range(m):
        f[i][0] = 1
    for j in range(n):
        f[0][j] = 1
    for i in range(1,m):
        for j in range(1,n):
            f[i][j] = f[i-1][j]+f[i][j-1]
    return f[m-1][n-1]

优化:

def uniquePaths( m, n):
    f = [1]*m
    for j in range(1,n):
        for i in range(1,m):
            f[i] = f[i-1]+f[i]
    return f[m-1]

最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

动态规划5大步骤:

1.定义状态:

即定义数据元素的含义,这里定义dp[i][j]为当前位置的最小路径和,i表示i列,j表示j行。注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 右下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要找的答案。

2.建立状态转移方程

因为从题目要求中可以看出,只能向右或向下移动。

所以到达dp[i][j]就可能是经过dp[i-1][j]到达,也可能是经过dp[i][j-1]到达。

不过这次不是计算所有可能路径,而是计算哪一个路径和是最小的,那么我们要从这两种方式中,选择一种,使得dp[i] [j] 的值是最小的,显然有

所以状态转移方程为:

;

arr[i][j] 表示当前位置路径值

3.设定初始值:

通过状态转移方程可以看出,i和j下标要从1开始,否则会导致数组溢出异常。因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了。同时每一个位置点代表到达当前位置的路径值,所以要设置最初的最小路径和即

当只有左边是矩阵边界时: 只能从上面来,即当

时, dp[i][j] = dp[i][j - 1] + arr[i] [j] ;

当只有上边是矩阵边界时: 只能从左面来,即当

时, dp[i][j] = dp[i - 1][j] + arr[i][j] ;

当左边和上边都是矩阵边界时: 即当i = 0, j = 0时,其实就是起点, dp[i][j] = arr[i][j]

4.状态压缩:

其实我们完全不需要建立 dp 矩阵浪费额外空间,直接遍历 arr[i][j] 修改即可。这是因为:arr[i][j] = min(arr[i - 1][j], arr[i][j - 1]) + arr[i][j] ;原 arr矩阵元素中被覆盖为 dp 元素后(都处于当前遍历点的左上方),不会再被使用到。

复杂度分析:

时间复杂度

: 遍历整个 arr 矩阵元素。

空间复杂度 O(1): 直接修改原矩阵,不使用额外空间

5.选出结果:

根据状态转移方程,求路径的总数,因此 arr[m-1] [n-1]表示的是到达最后位置的最小路径和

def MinPathSum(arr:[[int]]):
    for i in range(len(arr)):
        for j in range(len(arr[0])):
            if i == j == 0:
                continue
            elif i==0: arr[i][j] = arr[i][j-1] + arr[i][j]
            elif j==0: arr[i][j] = arr[i-1][j] + arr[i][j]
            else:
                arr[i][j] = min(arr[i-1][j],arr[i][j-1])+arr[i][j]
    return arr[i-1][j-1]

编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

解答

还是老样子,按照上面步骤来,并且我这里可以告诉你,90% 的字符串问题都可以用动态规划解决,并且90%是采用二维数组。

步骤一、定义状态:

由于我们的目的求将 word1 转换成 word2 所使用的最少操作数 。那我们就定义 dist[i] [j]的含义为:当字符串 word1 的长度为 i,字符串 word2 的长度为 j 时,将 word1 转化为 word2 所使用的最少操作次数为 dp[i] [j]

步骤二:建立状态转移方程

接下来我们就要找 dist[i] [j] 元素之间的关系了,比起其他题,这道题相对比较难找一点,但是,不管多难找,大部分情况下,dist[i] [j] 和 dist[i-1] [j]、disti] [j-1]、dist[i-1] [j-1] 肯定存在某种关系。因为我们的目标就是,从规模小的,通过一些操作,推导出规模大的。对于这道题,我们可以对 word1 进行三种操作

插入一个字符 删除一个字符 替换一个字符

由于我们是要让操作的次数最小,所以我们要寻找最佳操作。那么有如下关系式:

1、若word1[i] == word2[j],新字符相等,无需增加编辑距离即可直接转换,即dist[i][j] = dist[i-1][j-1]

2、如果我们 word1[i] 与 word2 [j] 不相等,这个时候我们就必须进行调整,而调整的操作有 3 种,我们要选择一种。三种操作对应的关系试如下(注意字符串与字符的区别):

(1)删除,最后操作是对word1删除1个字符,,意味着删掉 word1[i]后两单词匹配,
也就是在 word1[:i-1]word2[:j]完成转换的基础上增加一次删除操作实现,即 dist[i][j] = dist[i-1][j] + 1
(2)插入,最后操作是对word1插入1个字符,意味着 word2[j]由word1中新插入的字符对应,这是在 word1[:i]word2[:j-1]完成转换基础上增加一次插入操作实现,即 dist[i][j] = dist[i][j-1] + 1
(3)替换,最后操作是对word1替换1个字符,意味着 word1[i]替换成 word2[j]后实现两单词匹配,即在 word1[:i-1]word2[:j-1]完成转换基础上增加一次替换操作实现,即 dist[i][j] = dist[i-1][j-1] + 1

那么我们应该选择一种操作,使得 dp[i] [j] 的值最小,显然有

过程如图3所示

9c3a186f46fb91e053cb6dfccc6c452c.png
图3

第一行,是 word1 为空变成 word2 最少步数,就是插入操作

第一列,是 word2 为空,需要的最少步数,就是删除操作

步骤三、设定初始值:

显然,当 dist[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dist[0] [0….n] 和所有的 dist[0….m] [0]。

对于边界情况,一个空串和一个非空串的编辑距离为 D[i][0] = i 和 D[0][j] = j,D[i][0] 相当于对 word1 执行 i 次删除操作,D[0][j] 相当于对 word1执行 j 次插入操作。

步骤四、状态压缩:

2dbcfa33fde46bfa07e4a3ca5573aa5d.png

实际上,定义了dist矩阵后,发现每个方格距离仅与其左、上、左上三个值有关,进而又可以仅定义一个一维距离列表和一个辅助列表实现记录,从而优化空间复杂度.

这道题中,不能每次更新完 (i, j) 的值之后,就把 (i, j-1) 的值抛弃,也就是不能一边更新 dp[i] 的值,一边把 dp[i] 的旧值抛弃。以往只需要用到 (i-1, j) 和 (i, j-1)的值,但是这里还需要用到 (i-1, j-1) 这个值(但是这个值在以往的案例1 中,它会被抛弃掉)需要一个辅助列表 tmp 来时刻保存 (i-1,j-1) 的值。推导公式就可以从二维的

dist[i][j] = min(dist[i-1][j] , dist[i-1][j-1] , dist[i][j-1]) + 1

转化为一维的

dist[i] = min(dist[i-1], tmp, dist[i]) + 1。

进一步地,考虑定义的三种操作构成对称关系:在word1中删除字符与在word2中插入字符其转换效果是一致的,而替换更是对称操作,从而由word1转换为word2和由word2转换为word1其最小编辑距离也是一致的。基于此,可根据word1和word2字符长短进一步优化空间复杂度为最小的那个维度

步骤五、选出结果:

根据状态转移方程,求路径的总数,因此 dist[i] [j]表示的是最小编译距离

def MinDistance(word1:str,word2:str):
    m = len(word1)
    n = len(word2)
     # 有一个字符串为空串
    if n*m ==0:
        return n+m
     # dist 数组
    dist =[[0]*(n+1) for _ in range(m+1)]
    # 边界状态初始化
    for i in range(m+1):
        dist[i][0] = i
    for j in range(n+1):
        dist[0][j] = j
    # 计算所有 dist 值
    for i in range(1,m+1):
        for j in range(1,n+1):
            if word1[i-1] == word2[j-1]:
                dist[i][j] = dist[i-1][j-1]    
            else:
                dist[i][j] = min(dist[i-1][j],dist[i][j-1],dist[i-1][j-1])+1
    return dist[m][n]

优化后:

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        if len(word2)<len(word1):#保证word1是短单词
            word1, word2 = word2, word1
        m, n = len(word1), len(word2)
        dist = list(range(m+1))#word2长度为0时,由不同长度word1转换的编辑距离
        for i in range(1, n+1):
            tmp = [i]*(m+1)#word2长度为i时,由0长度的word1转换的编辑距离
            for j in range(1, m+1):
                if word1[j-1] == word2[i-1]:
                    tmp[j] = dist[j-1]
                else:
                    tmp[j] = min(tmp[j-1], dist[j], dist[j-1]) + 1
            dist = tmp
        return dist[-1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值