⭕️583. 两个字符串的删除操作
-
思路:核心代码就是最长公共子序列,但是需要注意的是结果
就是如果说公共子序列为0,则需要两个字符串长度的才行
如果有,就是 n + m ∗ 2 d p [ n ] [ m ] n+m*2dp[n][m] n+m∗2dp[n][m]
int minDistance(string word1, string word2) {
int n = word1.size();
int m = word2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
for(int i = 1; i < n + 1; i++){
for(int j = 1; j < m + 1; j++){
if(word1[i - 1] == word2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return n+m-2*dp[n][m];
}
712. 两个字符串的最小ASCII删除和
本质上换汤不换药~参考前面的公共子序列问题。
int minimumDeleteSum(string s1, string s2) {
int n = s1.size();
int m = s2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
int ascii_count = 0;
for(int i = 0; i < n; i++){
ascii_count += s1[i];
}
for(int i = 0; i < m; i++){
ascii_count += s2[i];
}
for(int i = 1; i < n + 1; i++){
for(int j = 1; j < m+1; j++){
if(s1[i-1] == s2[j-1]){
dp[i][j] = dp[i-1][j-1] + (int)s1[i-1];
}else{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
if(dp[n][m] == 0){
return ascii_count;
}
return ascii_count-2*dp[n][m];
}
1035. 不相交的线
-
思路:本质就是求最长公共子序列!!!!!!
因为这道题两个连线不想交,你想想,如果不想交,不就是公共子序列,按顺序找吗???
leetcode真的恶心
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
for(int i = 1; i < n + 1; i++){
for(int j = 1; j < m + 1; j++){
if(nums1[i - 1] == nums2[j - 1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][m];
}
392. 判断子序列
-
思路:这道题很简单,但是道题给了另一个不简单的方向,即要匹配的字符串就数十亿个,如何考虑?
-
简单的:
脑瘫写法:
bool isSubsequence(string s, string t) { int n = s.size(); int m = t.size(); if(n == 0){ return true; } int temp = 0; for(int i = 0; i < m; i++){ if(s[temp] == t[i]){ temp++; if(temp == n){ return true; } } } return false; }
双指针:
bool isSubsequence(string s, string t) { int i = 0; int j = 0; int n = s.size(); int m = t.size(); while(i < n && j < m){ if(s[i] == t[j]){ i++; } j++; } return i == n; }
-
进阶数十亿的:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10 亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
空间换时间的思想。关键点是:利用一个二维数组记录每个位置的下一个要匹配的字符的位置,这里的字符是
'a' ~ 'z'
,所以这个数组的大小是dp[n][26]
,n 为 T 的长度。那么每处理一个子串只需要扫描一遍 S 即可,因为在数组的帮助下我们对 T 是“跳跃”扫描的。如下图一,很自然的,当T索引为0时候,对应的第0列就是当前字符在T中出现位置的下标,没有的标-1就行;
/*ahbgdc这个T字符串构成的数组如下:重点在于从后往前构造会简单很多*/ 1 -1 -1 -1 -1 -1 -1 3 3 3 -1 -1 -1 -1 6 6 6 6 6 6 -1 5 5 5 5 5 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 4 4 4 4 -1 -1 -1 2 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
这个图应该能说明思想,其实就是跳表的思想,只要有-1就不比了,不用完全遍历t,时间换空间的思想。
bool isSubsequence(string s, string t) { t.insert(0, ' '); int len = t.size(); vector<vector<int>> vec(len, vector<int>(26, 0)); //创建一个二维数组 for(char char_t= 'a'; char_t <= 'z'; char_t++){ int next_pos = -1;//这个要放在循环里面!不能放在外面! for(int i = len-1; i >=0 ;i--){ vec[i][char_t-'a'] = next_pos; if(t[i] == char_t){ next_pos = i; } } } //开始匹配 int index = 0; for(char c : s){ index = vec[index][c - 'a']; if(index == -1){ return false; } } return true; }
115. 不同的子序列
-
思路:
说句题外话,就是两个字符串或者数字比较的话,大概率不是dp就是双指针,dp的话ij表示s1的前i个和s2的前j个
同时,子序列或者字串的话,切记,空串是任何序列的子序列!
因此这道题也很常规,但是难点就在于状态转移方程不好想
首先给出状态转移方程:
if (s[i - 1] == t[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; } else { dp[i][j] = dp[i - 1][j]; }
状态转移方程为啥是上述的?只有一个办法,就是画表。以后看到子序列问题的dp ,画表就完事儿了!!!!
int numDistinct(string s, string t) { int n = s.size(); int m = t.size(); if(n < m){ return 0; } vector<vector<long long>> dp(n+1, vector<long long>(m+1, 0)); //切记!空字符串匹配为1 for(int i = 0; i < n + 1; i++){ dp[i][0] = 1; } for(int i = 1; i < n + 1; i++){ for(int j = 1; j < m + 1; j++){ if(s[i - 1] == t[j - 1]){ dp[i][j] = dp[i - 1][j - 1] + dp[i -1][j]; if(dp[i][j]>INT_MAX){ dp[i][j]=0; } }else{ dp[i][j] = dp[i - 1][j]; } } } return dp[n][m]; }
⭕️72. 编辑距离
-
前面说过两个单词或者两个字符串的问题不是双指针就是动态规划问题,很明显这个这么难的问题就是动态规划。
同时又是两个字符串比较,那肯定 d p [ i ] [ j ] dp[i][j] dp[i][j]啊!主要是这个状态转移方程不太好想
当两个字符不相等的时候,有三种操作:删除、插入和替换!!!这才是状态转移方程的重点!!
当
word1[i] == word2[j]
,dp[i][j] = dp[i-1][j-1]
;当
word1[i] != word2[j]
,dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
其中,
dp[i-1][j-1]
表示替换操作,dp[i-1][j]
表示删除操作,dp[i][j-1]
表示插入操作。
int third_min(int a, int b, int c){
int temp = a < b ? a : b;
return temp < c ? temp : c;
}
int minDistance(string word1, string word2) {
int n = word1.size();
int m = word2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
//确定完dp后还是得考虑极端情况
for(int i = 0; i < m + 1; i++){
dp[0][i] = i ;
}
for(int i = 0; i < n + 1; i++){
dp[i][0] = i ;
}
for(int i = 1; i < n + 1; i++){
for(int j = 1; j < m + 1; j++){
if(word1[i - 1] == word2[j - 1]){
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = third_min(
dp[i - 1][j - 1] + 1, //替换
dp[i - 1][j] + 1, //word1删除一个字符然后换和word2相同的,这个是一个操作
dp[i][j - 1] + 1 //word2删除一个字符然后换和word1相同的,这个是一个操作
);
}
}
}
return dp[n][m];
}