1143. 最长公共子序列
给定两个字符串text1和text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
1. 确定 dp
以及下标的含义
dp[i][j]
:长度为[0, i - 1]
的字符串text1
与长度为[0, j - 1]
的字符串text2
的最长公共子序列为dp[i][j]
2. 确定递推公式
两种情况: text1[i - 1]
与 text2[j - 1]
相同,text1[i - 1]
与 text2[j - 1]
不相同
如果text1[i - 1]
与 text2[j - 1]
相同,那么找到了一个公共元素,所以dp[i][j]
= dp[i - 1][j - 1]
+ 1;
如果text1[i - 1]
与 text2[j - 1]
不相同,那就看看text1[0, i - 2]
与text2[0, j - 1]
的最长公共子序列 和 text1[0, i - 1]
与 text2[0, j - 2]
的最长公共子序列,取最大的。
即:dp[i][j]
= max(dp[i - 1][j], dp[i][j - 1])
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
3. dp
数组初始化
先看看dp[i][0]
应该是多少呢?
test1[0, i-1]
和空串的最长公共子序列自然是0,所以dp[i][0]
= 0;
同理dp[0][j]
也是0。
其他下标都是随着递推公式逐步覆盖,初始为多少都可以,那么就统一初始为0。
let dp = Array.from(Array(text1.length+1), () => Array(text2.length+1).fill(0));
4. 确定遍历顺序
从递推公式,要从前向后,从上到下来遍历这个矩阵。
完整代码
const longestCommonSubsequence = (text1, text2) => {
let dp = Array.from(Array(text1.length + 1), () => Array(text2.length + 1).fill(0));
for (let i = 1; i <= text1.length; i++) {
for (let j = 1; j <= text2.length; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
}
}
return dp[text1.length][text2.length];
};
583. 两个字符串的删除操作
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
示例:
输入: "sea", "eat"
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"
1. 确定 dp
以及下标的含义
dp[i][j]
:以 i-1
为结尾的字符串 word1
,和以 j-1
位结尾的字符串 word2
,想要达到相等,所需要删除元素的最少次数。
这里dp
数组的定义有点点绕,大家要撸清思路。
2. 确定递推公式
- 当
word1[i - 1]
与word2[j - 1]
相同的时候 - 当
word1[i - 1]
与word2[j - 1]
不相同的时候
当 word1[i - 1]
与 word2[j - 1]
相同的时候,dp[i][j]
= dp[i - 1][j - 1]
;
当 word1[i - 1]
与 word2[j - 1]
不相同的时候,有三种情况:
情况一:删word1[i - 1]
,最少操作次数为dp[i - 1][j] + 1
情况二:删 word2[j - 1]
,最少操作次数为dp[i][j - 1] + 1
情况三:同时删 word1[i - 1]
和 word2[j - 1]
,操作的最少次数为dp[i - 1][j - 1] + 2
那最后当然是取最小值,所以当 word1[i - 1]
与 word2[j - 1]
不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
3. dp数组初始化
dp[i][0]
和 dp[0][j]
是一定要初始化的。
dp[i][0]:word2
为空字符串,以i-1为结尾的字符串word2
要删除多少个元素,才能和word1
相同呢,很明显dp[i][0] = i
。
dp[0][j]
的话同理,所以代码如下:
for (let i = 1; i <= word1.length; i++) {
for (let j = 1; j <= word2.length; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2);
}
}
}
4. 确定遍历顺序
从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1)
; 和dp[i][j] = dp[i - 1][j - 1]
可以看出dp[i][j]
都是根据左上方、正上方、正左方推出来的。所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]
可以根据之前计算出来的数值进行计算。
完整代码
const minDistance = (word1, word2) => {
let dp = Array.from(Array(word1.length + 1), () => Array(word2.length + 1).fill(0));
for (let i = 1; i <= word1.length; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= word2.length; j++) {
dp[0][j] = j;
}
for (let i = 1; i <= word1.length; i++) {
for (let j = 1; j <= word2.length; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2);
}
}
}
return dp[word1.length][word2.length];
};
712. 两个字符串的最小ASCII删除和
给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。
示例 1:
输入: s1 = "sea", s2 = "eat"
输出: 231
解释: 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。
在 "eat" 中删除 "t" 并将 116 加入总和。
结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。
示例 2:
输入: s1 = "delete", s2 = "leet"
输出: 403
解释: 在 "delete" 中删除 "dee" 字符串变成 "let",
将 100[d]+101[e]+101[e] 加入总和。在 "leet" 中删除 "e" 将 101[e] 加入总和。
结束时,两个字符串都等于 "let",结果即为 100+101+101+101 = 403 。
如果改为将两个字符串转换为 "lee" 或 "eet",我们会得到 433 或 417 的结果,比答案更大。
完整代码
const minimumDeleteSum = (word1, word2) => {
let dp = Array.from(Array(word1.length + 1), () => Array(word2.length + 1).fill(0));
for (let i = 1; i <= word1.length; i++) {
dp[i][0] = dp[i - 1][0] + word1[i - 1].charCodeAt();
}
for (let j = 1; j <= word2.length; j++) {
dp[0][j] = dp[0][j - 1] + word2[j - 1].charCodeAt();
}
for (let i = 1; i <= word1.length; i++) {
for (let j = 1; j <= word2.length; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + word1[i - 1].charCodeAt(), dp[i][j - 1] + word2[j - 1].charCodeAt(), dp[i - 1][j - 1] + (word1[i - 1].charCodeAt() + word2[j - 1].charCodeAt()));
}
}
}
return dp[word1.length][word2.length];
};
72. 编辑距离
给你两个单词 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')
完整代码
var minDistance = function (word1, word2) {
let len1 = word1.length, len2 = word2.length
let dp = new Array(len1 + 1).fill(0).map(v => new Array(len2 + 1).fill(0));
for (let i = 0; i <= len1; i++) {
dp[i][0] = i
}
for (let j = 0; j <= len2; j++) {
dp[0][j] = j
}
for (let i = 1; i <= len1; i++) {
for (let j = 1; j <= len2; j++) {
if (word1.charAt(i - 1) === word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]
} else {
dp[i][j] = 1 + Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1])
}
}
}
return dp[len1][len2]
};
1035. 不相交的线
在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:
nums1[i] == nums2[j]
且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
示例 1:
输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。
示例 2:
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
输出:3
示例 3:
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
输出:2
完整代码
const maxUncrossedLines = (text1, text2) => {
let dp = Array.from(Array(text1.length + 1), () => Array(text2.length + 1).fill(0));
for (let i = 1; i <= text1.length; i++) {
for (let j = 1; j <= text2.length; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
}
}
return dp[text1.length][text2.length];
};