寻找不同路径和
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个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.状态压缩:
上面时间复杂度:
即优化数组空间,每次状态的更新只依赖于前一次的状态,优化一般作用于多维数组,观察是否可以将多维数组以一维数组来动态表示,即用一维数组来保存上次的状态,动态计算并替换当前位置下的路径数最新值。这道题的优化方法是存在的。
当我们要填充第三行的值的时候,我们需要用到第一行的值吗?根据公式 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。
以往的二维的时候, 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,就变成负数了,数组就会出问题了。同时每一个位置点代表到达当前位置的路径值,所以要设置最初的最小路径和即
当只有左边是矩阵边界时: 只能从上面来,即当
当只有上边是矩阵边界时: 只能从左面来,即当
当左边和上边都是矩阵边界时: 即当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 元素后(都处于当前遍历点的左上方),不会再被使用到。
复杂度分析:
时间复杂度
空间复杂度 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所示
第一行,是 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 次插入操作。
步骤四、状态压缩:
实际上,定义了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]