一、最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
using namespace std;
//最长公共子序列
//DP
//长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
void longestCommonSubsequence(string& s1, string& s2, int &res) {
vector<vector<int>> dp(s1.size() + 1, vector<int>(s2.size() + 1, 0));
for (int i = 1; i <= s1.size(); i++) {
for (int j = 1; j <= s2.size(); j++) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
res = dp[s1.size()][s2.size()];
//return dp[s1.size() - 1][s2.size() - 1];
}
int main(int argc, char* argv[]) {
string s1 = "";
string s2 = "";
getline(cin, s1);
getline(cin, s2);
int res = 0;
longestCommonSubsequence(s1, s2, res);
cout << res << endl;
return 0;
}
二、KMP算法
求解一个字符串m在另一个字符串s中出现的首个索引位置使用kmp算法。详细注释见代码。
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
using namespace std;
//返回数组的首地址
//nextArray的第i个值是从0 -- i-1长的字符串中 的最长相等前后缀的长度
int* getNextArray(string m) {
int* nextArray = new int[m.size()];
if(m.size() == 1){
nextArray[0] = -1;//人为规定为-1
return nextArray;
}
nextArray[0] = -1;//人为规定为-1
nextArray[1] = 0;//人为规定为0
int cn = 0;//cn表示哪个字符在和i - 1 位置的字符进行比较(前缀的下一个位置);也表示当前使用的信息的长度是多少
for (int i = 2; i < m.size();) {
//根据nextArray[i-1]计算nextArray[i]
if (m[i - 1] == m[cn]) {
/*nextArray[i] = cn + 1;
i++;
cn++;*/
nextArray[i++] = ++cn;
}
else if (cn > 0) { //m[i - 1] != m[cn]
cn = nextArray[cn]; //cn往前跳
}
else { //cn不能再往前跳
nextArray[i++] = 0; //没有相等的前后缀 nextArray[i] = 0
}
}
return nextArray;
}
int kmpGetIndexOf(string &s, string &m) {
if (s.size() == NULL || m.size() == NULL || m.size() < 1 || s.size() < m.size()) {
return -1;
}
int i = 0, j = 0;
int *nextArray = getNextArray(m);//拿到next数组
while (i < s.size() && j < m.size()) {
if (s[i] == m[j]) {
i++;
j++; //只有匹配相等j才自加 如果越界则匹配成功
}
else if (nextArray[j] == -1) { //else if(j == 0) //j无法再向前跳
i++; //s的下标加1
}
else {
j = nextArray[j];//j跳回到next数组中对应的位置
}
}
return j == m.size() ? i - j : -1; //匹配成功返回两个指针的差值
}
//返回m在s中出现的第一个索引位置
int main(int argc, char* argv[]) {
string s = ""; string m = "";
char s_c, s_m;
getline(cin, s);
getline(cin, m);
int res = 0;
res = kmpGetIndexOf(s, m);
cout << res << endl;
return -1;
}
三、有关字符串句子的单词反转问题
先把句子中所有字符串取出放入字符串数组,再对数组中的字符串进行操作后重新连接即可,具体问题具体细节还需要按题目要求分析
而遍历句子取字符串的思路,就是遇到字符把它放入临时字符串,遇到空格或者标点(如果有标点),就把临时字符串输出,并且清空。
以下代码中的res即为字符串数组,求解res的过程即是模板。
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
using namespace std;
void reverseWords(string& s1, string& s2) {
s1 += ' '; //补一个空格,避免最后一个单词无法取出
string tmp = "";//存储单个单词的临时字符串
vector<string> res;//存储每个单词的字符串数组
for (char ch : s1) {
if (ch == ' ') { //如果是空格
if (!tmp.empty()) { //如果s1的前后有空格 要这行 否则不需要
res.push_back(tmp);
tmp.clear();
}
}
else {//如果不是空格
tmp += ch;
}
}
//到这里 res中存储的是每个单词
reverse(res.begin(), res.end()); //反转
for (auto str : res) {
s2 += str + ' '; //重新拼接为反转后的字符串句子
}
s2.pop_back(); //将最后一个空格推出去
//return s2;
}
int main(int argc, char* argv[]) {
string s1 = "";
string s2 = "";
getline(cin, s1);
reverseWords(s1, s2);
cout << s2 << endl;
return 0;
}
四、子序列判断问题
1、判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1: 输入:s = “abc”, t = “ahbgdc” 输出:true
示例 2: 输入:s = “axc”, t = “ahbgdc” 输出:false
class Solution {
public:
//动态规划
//dp[i][j]表示s中以下标i-1为结尾的字符串 和 t中以下标j-1为结尾的字符串 相等子序列的长度
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
for(int i = 1; i <= s.size(); i++){
for(int j = 1; j <= t.size(); j++){
if(s[i - 1] == t[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else{
dp[i][j] = dp[i][j - 1];//相当于t要删除元素,t如果把当前元素t[j-1]删除,那么dp[i][j] 的数值就是 看s[i-1]与 t[j-2]的比较结果了
}
}
}
//如果dp[s.size()][t.size()] 与 字符串s的长度相同说明:s与t的最长相同子序列就是s,那么s 就是 t 的子序列
if(dp[s.size()][t.size()] == s.size()) return true;
return false;
}
};
2、不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
题目数据保证答案符合 32 位带符号整数范围。
//dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
for (int j = 1; j < t.size(); j++) dp[0][j] = 0;
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); 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];
}
}
}
return dp[s.size()][t.size()];
}
};
五、两个字符串的删除操作
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
示例:
输入: “sea”, “eat”
输出: 2 解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"
//除了最长公共子序列之外的字符都是必须删除的,最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1, vector<int>(word2.size()+1, 0));
for (int i=1; i<=word1.size(); i++){
for (int j=1; j<=word2.size(); 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 word1.size()+word2.size()-dp[word1.size()][word2.size()]*2;
}
};
六、有关回文串的问题
1、验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
class Solution {
public:
bool isPalindrome(string s) {
string sgood; //存储转换好可以进行比较的字符串
for (char ch: s) {
if (isalnum(ch)) { //如果是字母或者数字(排除空格)
sgood += tolower(ch);//大写字母转换为小写字母 //方便比较
}
}
//双指针 挨个字母进行比较
int n = sgood.size();
int left = 0, right = n - 1;
while (left < right) {
if (sgood[left] != sgood[right]) {
return false;
}
++left;
--right;
}
return true;
}
};
2、回文子串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:“abc” 输出:3 解释:三个回文子串: “a”, “b”, “c”
示例 2:
输入:“aaa” 输出:6 解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”
提示:
输入的字符串长度不会超过 1000 。
//dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
class Solution {
public:
int countSubstrings(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));//初始化全为false
int result = 0;
//一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j < s.size(); j++) {//j>=i 只填充dp数组的右上半部分
if (s[i] == s[j]) {
if (j - i <= 1) { //必是回文子串
result++;
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { //[i, j]区间长度大于1
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
};
3、最长回文子序列
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1: 输入: “bbbab” 输出: 4 一个可能的最长回文子序列为 “bbbb”。
示例 2: 输入:“cbbd” 输出: 2 一个可能的最长回文子序列为 “bb”。
提示:
1 <= s.length <= 1000
s 只包含小写英文字母
//dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
class Solution {
public:
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for (int i = 0; i < s.size(); i++) dp[i][i] = 1;//一个字符的回文子序列长度就是1。
for (int i = s.size() - 1; i >= 0; i--) {//从下至上遍历
for (int j = i + 1; j < s.size(); j++) {
if (s[i] == s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2; //最两边相等 长度加2
} else {
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);//最两边不相等,分别加其中一个,所构成的最长回文子序列长度的最大值
}
}
}
return dp[0][s.size() - 1];
}
};