暴力法
- O(N3)
// 暴力破解
// 例如:abcddcb
// 1.其子串为
// ab
// abc
// abcd
// abcdd
// abcddc
// abcddcb
// bc
// bcd
// bcdd
// bcddc
// bcddcb
// cd
// cdd
// ......
// 2.一一判断所有的这些子串是否是回文串
// O(N3)
void longest(char * str)
{
if (str == NULL)
{
return;
}
int n = strlen(str);
if (n <= 1)
{
return;
}
int start = 0, end = 0, max = 0,l = 0; // 最长子串起始点、终点、长度
for (int i = 0; i < n; i++) // 求出字符串的每一个子串,然后判断其是否是最长子串
{
for (int j = i + 1; j < n; j++) // 得到一个子串str[i~j]
{
//判断子串str[i~j]是够是回文子串
int startp = i, endp = j;
int tmpStart = i, tmpEnd= j ;
end = j;
l = 0;
while (startp <= endp)
{
if (*(str + startp) == *(str + endp))
{
startp++;
endp--;
l++;
}
break;
}
// 判断当前子串是否为最长
if (l > max)
{
max = l;
start = tmpStart;
end = tmpEnd;
}
}
}
cout << start << "\t" << end << endl;
cout << "------------------------" << endl;
for (int i = start; i <= end; i++)
{
cout << *(str + i) << "\t";
}
}
暴力法改进版
- O(N2)
// 时间复杂度O(n^2),空间O(1)
// 依次扫描字符串 以每个字符为中心 查找其最长的回文子串 其中要考虑奇数偶数的问题
void longestStr(char * str)
{
int n = strlen(str);
int maxl = 0, maxr = 0;
int max = 0;
for (int i = 0; i < n; i++)
{
int l = i - 1;
int r = i + 1;
int tmpl = 0;
// 偶数的情况 如 aa 它的字符串长度为偶数
while (l >= 0 && r <= n - 1 && *(str + r) == *(str + i))
{
r++;
}
// 奇数的情况 如bab
while (l >= 0 && r <= n - 1 && *(str + r) == *(str + l))
{
r++;
l--;
}
tmpl = r - l + 1;
if (tmpl > max)
{
max = tmpl;
maxl = l + 1;
maxr = r - 1;
}
}
cout << maxl << "\t" << maxr << endl;
cout << "------------------------" << endl;
for (int i = maxl; i <= maxr; i++)
{
cout << *(str + i) << "\t";
}
}
动态规划
分析
删除暴力解法中有很多重复的判断。很容易想到动态规划(DP),时间复杂度O(n^2),空间O(n^2)。
在DP算法中,每次要遍历一遍字符串,同时要判断子串是否是回文,这里显然是存在重复计算的。这给我们改进提供了可能。那么该如何更好的判断回文呢?我们设定P[i][j]
- dp[i][j] 表示子串s[i…j]是否是回文
- dp[i][j] 为true时,表示str[i..j]为回文
- dp[i][j] 为false时,表示str[i..j]不是回文
则,存在以下几种情况
- i==j时,P[i][j]=true
- j==i+1时,P[i][j]=str[i]==str[i]
- 其他,P[i][j]=P[i+1][j-1]&&(str[i]==str[j])
这个P该如何构建呢?根据其状态转移的方程,P[i][j]所代表的字符串,长度从1开始变化,逐渐到整个字符串,是这样的一个构建的过程,所以外层循环应该是所要判断的字串的长度,而内层循环为从每个字符开始对p[i][j]进行判断赋值。
代码如下:
// 动态规划
void longestStrDP(char * str)
{
int n = strlen(str); // 字符串的长度
int max = 0; // 记录最大回文长度
int startIndex; // 最大回文的起始索引
bool dp[1000][1000] = { false }; // 状态矩阵
// 初始化矩阵dp
dp[0][0] = true;
for (int i = 1; i < n; i++)
{
dp[i][i] = true; // 长度为1的都是回文
dp[i][i - 1] = true; // 容易遗忘的初始化,在后续判断中会用到
}
// 连着
for (int i = 0; i < n; i++)
{
//dp[i][i+1] = (*(str + i) == *(str + i + 1));
if (*(str + i) == *(str + i + 1))
{
max = 2;
startIndex = i;
}
}
for (int len = 2; len < n; len++) // 枚举子字符串的长度
{
for (int i = 0; i < n - len +1; i++) // 在一定长度下,从第一个开始枚举所有的字符串
{ // i为起始
int j = i + len - 1; // j为末尾
//dp[i][j] = (*(str+i) == *(str+j) && dp[i+1][j-1]);
if (*(str + i) == *(str + j) && dp[i + 1][j - 1])
// 这里条件会出现i+1和j-1之后 会出现dp[m][n] 其中m>n
// 如字符串"caad",若在此刻状态方程为dp[1][2]
// dp[i + 1][j - 1]这条件就变成了dp[2][1],值为false,从而无法给dp[1][2]赋值true,导致后续所有的判断都受影响而不能进行
// 而此时应该是可以通过的条件而执行下面的代码
// 故在初始化阶段应该给dp[i][i-1]赋值true
{
dp[i][j] = true;
max = len;
startIndex = i;
}
}
}
cout << startIndex << "\t" << startIndex + max - 1 << endl;
cout << "------------------------" << endl;
for (int i = startIndex; i <= startIndex + max - 1; i++)
{
cout << *(str + i) << "\t";
}
}