lc63. 不同路径 II(MD)

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

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

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

示例 1:

img

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

img

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01

和昨天的 最小路径和 比较相似,但是在预处理的时候,需要特别对障碍物进行处理

class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) {
return 0;
}
int[][] dp = new int[m][n];
for (int i = 0; i < m && obstacleGrid[i][0] != 1; i++) {
dp[i][0] = 1;
}
for (int i = 0; i < n && obstacleGrid[0][i] != 1; i++) {
dp[0][i] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
continue;
}
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}

lc97. 交错字符串(MD)

给定三个字符串 s1s2s3,请你帮忙验证 s3 是否是由 s1s2 交错 组成的。

两个字符串 st 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

  • s = s1 + s2 + ... + sn
  • t = t1 + t2 + ... + tm
  • |n - m| <= 1
  • 交错s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...

注意:a + b 意味着字符串 ab 连接。

示例 1:

img

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出:true

示例 2:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false

示例 3:

输入:s1 = "", s2 = "", s3 = ""
输出:true

提示:

  • 0 <= s1.length, s2.length <= 100
  • 0 <= s3.length <= 200
  • s1s2、和 s3 都由小写英文字母组成

**进阶:**您能否仅使用 O(s2.length) 额外的内存空间来解决它?


首先这道题肯定不能用双指针法。根据力扣官方的解释,当样例为 s1 = aabcc , s2 = dbbca , s3 = aadbbcbcac 时,得到的结果与正确答案相悖

此题应考虑使用DP,理论来讲这道题也能用DFS,当然用DP是最好的。首先对字符串长度做判定:若s1+s2s3|s_1| + |s_2| \ne |s_3| ,则s3s_3 一定不能由s1s_1s2s_2 交错组成

符合上述字符串条件之后,应使用DP。定义dp(i,j)dp(i, j) 表示s1s_1 的前ii 个元素与s2s_2 的前jj 个元素是否能够交错组成s3s_3 的前i+ji + j 个元素。如果s1s_1 的第ii 个元素和s3s_3 的第i+ji + j 个元素相等,那么s1s_1 的前ii 个元素与s2s_2 的前jj 个元素是否能够交错组成s3s_3 的前i+ji + j 个元素取决于s1s_1 的前i1i-1 个元素与s2s_2 的前j1j-1 个元素是否能够交错组成s3s_3 的前i+j1i + j-1 个元素。也就是说,此时dp(i,j)dp(i, j) 取决于dp(i1,j)dp(i - 1, j),若dp(i1,j)dp(i - 1, j) 为真,则dp(i,j)dp(i, j) 也为真。同样的,如果s2s_2 的第jj 个元素和s3s_3 的第i+ji + j 个元素相等且dp(i,j1)dp(i, j -1) 为真,则dp(i,j)dp(i,j) 也为真。

故得到下面的转移方程:

dp(i,j)=[dp(i1,j)&&s1(i1)==s3(p)][dp(i,j1)&&s2(j1)==s3(p)]dp(i, j) = [dp(i - 1, j) \&\& s_1(i - 1) == s_3(p)] || [dp(i, j - 1) \&\& s_2(j - 1) == s_3(p)]

其中,p=i+j1p = i + j - 1 ,边界条件dp(0,0)=truedp(0, 0) = true

class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
int n = s1.length(), m = s2.length(), t = s3.length();
if (n + m != t) {
return false;
}
boolean[][] dp = new boolean[n + 1][m + 1];
dp[0][0] = true;
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
int p = i + j - 1;
if (i > 0) {
dp[i][j] = dp[i][j] || (dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(p));
}
if (j > 0) {
dp[i][j] = dp[i][j] || (dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(p));
}
}
}
return dp[n][m];
}
}

这道题算是假Mid题了,很明显就是以前的Hard题然后被下放到Mid的


lc72. 编辑距离(MD)

给你两个单词 word1word2请返回将 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')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1word2 由小写英文字母组成

事先声明:这道题其实就是一道Hard题!不要被它的Mid标识所迷惑了,这道题从题目理解到题目解析再到代码量完完全全达到了Hard题的标准!

这道题还是老样子,使用DP

这道题要求对一个单词进行如下三种操作:

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

题目给定了A和B两个单词,所以我们可以有共计6种操作方法

但是,我们发现:

  • 对单词A删除一个字符和对单词B插入一个字符是等价的
  • 对单词B删除一个字符和对单词A插入一个字符是等价的
  • 对单词A替换一个字符和对单词B替换一个字符是等价的

那么实际上,操作有且仅有三种:

  • 在单词A中插入字符
  • 在单词B中插入字符
  • 修改单词A的字符

我们以 A=horse, B=ros 为例子,解释一下如何将这个大问题分解为规模较小的子问题

  • 在单词A中插入字符 :如果我们知道 horseros 的编辑距离为 a,那么显然 horseros 的编辑距离不会超过 a + 1。这是因为我们可以在 a 次操作之后将 horsero 变为相同的字符串,只需要额外的 1 次操作,在单词 A 的末尾添加字符 s,就能在 a + 1 次操作后将 horsero 变为相同的字符串
  • 在单词B中插入字符 :如果我们知道 horsros 的编辑距离为 b , 那么显然 horseros 的编辑距离不会超过 b + 1
  • 修改单词A的一个字符 :如果我们知道 horsro 的编辑距离是 c , 那么显然 horseros 的编辑距离不会超过 c + 1

假设用dp(i,j)dp(i,j) 表示 A 的前 i 个字母和 B 的前 j 个字母之间的编辑距离

综上所述,当我们获得dp(i,j1)dp(i, j - 1)dp(i1,j)dp(i - 1, j)dp(i1,j1)dp(i - 1, j - 1) 之后就能计算出dp(i,j)dp(i, j) 的值了

那么就能得到如下的状态转移方程

  • AB 的最后一个字母相同:

dp(i,j)=min[dp(i,j1)+1,dp(i1,j)+1,dp(i1,j1)]=1+min[dp(i,j1),dp(i1,j),dp(i1,j1)1]dp(i, j) = min[dp(i, j - 1) + 1, dp(i - 1, j) + 1, dp(i - 1, j - 1)] = 1+min[dp(i, j - 1), dp(i - 1, j), dp(i - 1, j - 1) - 1]

  • AB 的最后一个字母不同:

dp(i,j)=1+min[dp(i,j1),dp(i1,j),dp(i1,j1)]dp(i, j) = 1 + min[dp(i, j - 1), dp(i - 1, j), dp(i - 1, j - 1)]

对于边界情况,一个空串和一个非空串的编辑距离为dp(i,0)=idp(i, 0) = idp(0,j)=jdp(0, j) = jdp(i,0)=idp(i, 0) = i 相当于对 word1 进行 i 次删除操作,dp(0,j)=jdp(0, j) = j 相当于对 word2 进行 j 次插入操作

class Solution {
public int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();

if (n * m == 0) {
return n + m;
}

int[][] dp = new int[n + 1][m + 1];
for (int i = 0; i <= n; i++) {
dp[i][0] = i;
}
for (int i = 0; i <= m; i++) {
dp[0][i] = i;
}

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int left = dp[i - 1][j] + 1;
int down = dp[i][j - 1] + 1;
int left_down = dp[i - 1][j - 1];
if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
left_down += 1;
}
dp[i][j] = Math.min(left, Math.min(down, left_down));
}
}
return dp[n][m];
}
}