回文串题目刷题过程中还是有些的,做个总结
概念:回文串,就是从左向右读和从右向左读 是一样的,例如 abcba
回文子序列:子序列的不一定是连续的,只要是能组成从左向右 catebleaf 子序列为aebea
(1)判断是否是回文串
根据字符串的++和--是否相等来给出是否是回文串标识,主体框架是这样,但是不同的题目要求不一样,比如说空串也是回文串、忽略非字母和数字之外的字符等,
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写,本题中,我们将空字符串定义为有效的回文串
输入: "A man, a plan, a canal: Panama"
输出: true
输入: "race a car"
输出: false
int JudePal(const string ss) {
int palFlag = 1;
int sLen = ss.length();
if (sLen < 2) {
return 0;
}
int i = 0;
int j = sLen - 1;
while (i < j) {
if (ss[i++] != ss[j--]) {
palFlag = 0;
break;
}
}
return palFlag;
}
(2)求最长回文子串(连续)
①最简单的做法,暴力遍历,存在占用内存过大问题,两个循环不断的遍历,判断字符串是否为回文,并比较长度
int judgePal(string s, int i, int j)
{
while (i < j) {
if (s[i++] != s[j--]) {
return 0;
}
}
return 1;
}
int getMaxPalLen(string s)
{
vector <string> res;
string tmpStr;
int maxLen = 0;
int tmpLen = 0;
int palLen = s.length();
for (int i = 0; i < palLen; i++) {
for (int j = i + 1; j < palLen; j++) {
if (judgePal(s, i, j)) {
tmpLen = j - i + 1;
if (tmpLen > maxLen) {//大于最大值,清空,push字符串
maxLen = tmpLen;
tmpStr = s.substr(i, maxLen);
res.clear();
res.push_back(tmpStr);
continue;
}
if (tmpLen == maxLen) {//等于最大值,不清空,push字符串
tmpStr = s.substr(i, j);
res.push_back(tmpStr);
}
}
}
}
for (int m = 0; m < res.size(); m++) {
cout << res[m] << endl;
}
return 0;
}
②中心扩展法
核心思想是通过遍历,遍历到某个字符的时候,以该字符为中心,不断的向两侧进行扩展比较,获取为回文串的最大长度和起始位置。然后再比较和下一个字符(右侧)为中心时,最长回文串的长度,两者比较长的保留下来,同时保留其start起始位置。
//中心扩展法
int getPalFromMiddle(string &s, int mid) {
int left = mid;
int right = mid;
while (left >= 0 && right < s.length()) {
if (s[left] == s[right]) {
left--;
right++;
continue;
}
break;
}
int tmpLen1 = right - left -1;//减去1,指的是之前左值和右值都加过了,所以需要减去相加的部分
left = mid;
right = mid + 1;
while (left >= 0 && right < s.length()) {
if (s[left] == s[right]) {
left--;
right++;
continue;
}
break;
}
int tmpLen2 = right - left - 1;
return tmpLen1 > tmpLen2 ? tmpLen1 : tmpLen2;
}
string getLongPal(string s) {
int sLen = s.length();
if (sLen < 2) {
return s;
}
int maxLen = 1;
int start = 0;
for (int i = 0; i < sLen; i++) {
int tmpLen = getPalFromMiddle(s, i);
if (tmpLen > maxLen) {
maxLen = tmpLen;
start = i - (maxLen - 1)/2;//获取回文串开始的左值,回文串一半长度=(中心值-1)/2,i-一半长度
}
}
string retStr = s.substr(start, maxLen);
cout << retStr << endl;
return retStr;
}
(3)求最长回文子序列(不连续)
当回文串为不连续的时候,求的回文子序列是可以跳跃的,比如abgycbya--->abcba为其回文子序列。回文串子序列可以用动态规划来处理,在使用动态规划的时候有两个重要的点需要明确
a.动态规划的初始值,及矩阵中的初始值,作为后续推导的初始值
b.动态规划的递推公式
看具体解题过程
求最长回文子序列bcdca
解题思路
思路很重要,有思路后,low的版本可以做递归,高的版本可以搞动态规划
对于字符串,如果想判断是不是回文,需要比较字符串的index字符和len-index字符是否相等,根据相等与否,做下一步操作
1)如果字符串ss[i]==ss[j],回文字符串长度length+2
2)如果字符ss[i] != ss[j],回文字符串长度=max(ss[i+1][j] ss[i][j-1])
这里可以将i和j看作字符串的头和尾来处理
递归
从解题思路上看 f(ss,i,i)---->ff(ss,i+1,j)或者f(ss,j,j-1)的长度,写递归即可
int getLongestPalSeqRecur(string ss, int i, int j) {
if (i >= j) {//i和j字符串的开头索引以及从后面向前的索引
return 1;//如果到最后 只剩下1个 返回值1,根据返回值是否为1,可以看下是否具体由回文串
}
if (ss[i] == ss[j]) {//如果相等,就进一步递归
return getLongestPalSeqRecur(ss, i + 1, j - 1) + 2;
}
else
{//不相等,比较两者的大小
if (getLongestPalSeqRecur(ss, i + 1, j) >= getLongestPalSeqRecur(ss, i, j - 1)) {
return getLongestPalSeqRecur(ss, i + 1, j);
}
else
{
return getLongestPalSeqRecur(ss, i, j - 1);
}
}
}
动态规划
动态规划需要从头到尾,从初始值累加到最后,填充矩阵。 关键点是如何选择遍历的方式,能把两个字符串的的右上角完全遍历处理以及动态规划的初始化变量的问题
初始化时 主要以字符串的间距作为索引i 从0开始 到 字符串的长度-1
当间距为0的时候,就是字符串中字符本身,所以长度都为1,这即是初始化值,接下来按照间隔增加
bcdca dp[j][j+i]
i = 1
j j+i str[j] str[j+i] [j+1][j+i] [j][j+i-1]
0 1 str[0] str[1] [1][1] [0][0]
1 2 str[1] str[2] [2][2] [1][1]
2 3 str[2] str[3] [3][3] [2][2]
3 4 str[3] str[4] [4][4] [3][3]
i = 2
j j+i str[j] str[j+i]
0 2 str[0] str[2] [1][2] [0][1]
1 3 str[1] str[3] 相等 j+1 j+i-1 [2][2]
2 4 str[2] str[4] [3][4] [2][3]
i = 3
j j+i str[j] str[j+i]
0 3 str[0] str[3] [1][3] [0][2]
1 4 str[1] str[4] [2][4] [1][3]
i = 4
j j+i str[j] str[j+i]
0 4 str[0] str[4] [1][4] [0][3]
0 1 2 3 4
b c d c a
0 b 1 1 1 3 3
1 c 1 1 3 3
2 d 1 1 1
3 c 1 1
4 a 1
代码
int getLongestPalSeqDp(string ss) {
int strLen = ss.length();
if (strLen <= 1) {
return 0;//只有一个字符以及之下为0
}
//生成矩阵
int **p = new int *[strLen];
for (int i = 0; i < strLen; i++) {
p[i] = new int [strLen];
}
//矩阵初始化为0
for (int m = 0; m < strLen; m++) {
for (int n = 0; n < strLen; n++) {
p[m][n] = 0;
cout << p[m][n];
}
cout << endl;
}
cout << "------------" << endl;
//初始值初始化,对角线初始化ok
for (int i = 0; i < strLen; i++) {
p[i][i] = 1;
}
//Dp处理,设p[j][j+i]为当前两个索引的索引位置
for (int i = 1; i < strLen; i++) {//i作为间隔,从0开始没有意义,从1开始
for (int j = 0; j + i < strLen; j++) {
if (ss[j] == ss[j + i]) {
p[j][j+i] = p[j + 1][j + i - 1] + 2;
}
else {
p[j][j + i] = max(p[j+1][j + i],p[j][j + i -1]);
}
}
}
for (int i = 0; i < strLen; i++) {
for (int j = 0; j < strLen; j++) {
cout << p[i][j];
}
cout << endl;
}
return 0;
}
动态规划结果图