longest palindromic substring(最长回文子串)

85 篇文章 0 订阅
58 篇文章 1 订阅

题记: 尼玛没想到一个简单的回文字符串这么多的解法。 跪了。 不同的perspective, 看到的问题的不同的结构, 进而会完全影响你解决问题的思路。

注意, substring和subsequence 是有区别的。substring属于subsequence, 但是subsequence 不一定是substring。

给定一个string T = str[0...n-1], 该字符串的一个子串substring就是截取连续的(包括全部)的一部分subT = [i, ...j], 其中, 0<= i and j <= n-1。 但是subsequence则是选择相关的字符, 保持其在原字符串中的相对顺序, 然后组成的新的string就是subsequence, 别混淆了。 

最长回文子串(LPS)就是从给定的字符串中找出最长的一个子字符串, 这个子字符串从左往右读和从右往左读, 读出的结果是一样的。

例如:aba, a, abba均是回文子串。 在例如, S = "abcbcbb"的LPS 是“bcbcb”。

(方法一) bruteforce的办法: 最简单的办法就是价差字符串的每一个子字符串是否为palindrome or not。  我们可以运行三个loops, 外层两个loops 一个个的选择字符串所有可能的子字符串, 内层循环检查选的该子字符串是否是palindorme的。 

#include <string>

#include <iostream>

using namespace std;

/*判断str[i..j]是否为回文串*/
bool isPalindrome(string str, int Start, int End) {
	while(str[Start++] == str[End--] && Start <= End) { }
	if(str[--Start] != str[++End]) {
		return false;
	} else {
		return true;
	}
}

/*返回最长回文子串长度*/
string longestPald(string str) {
	int len = str.length();
	int longest = 1;
	if(len == 1) {
		return str.substr(0, 1);
	}
    int longestStart = 0;
	for(int i = 0; i<len; i++) {
		for(int j = i + 1; j < len; j++) {
			if(isPalindrome(str, i, j)) {
				if(longest < j - i + 1) {
                    longestStart = i;
                    longest = j - i + 1;
				}
				//longest = (longest < j-i+1 ? j-i+1: longest);
			}
		}
	}
	cout << longest << endl;
	return str.substr(longestStart, longest);
}
int main() {
    string s = "forgeeksskeegfor";

    cout << longestPald(s) << endl;

    return 0;
}

运行结果如下:



分析: 时间复杂度:    O(n^3)

           辅助空间复杂度: O(1)


(方法二) 动态规划的办法:  我们maintain一个boolean的table[n][n], 然后自底向上的方式填充这个table。 如果子字符串str[i, j]是palindrome的 我们就把table[i][j]记为true, 否则就记为false。 为了计算table[i][j], 我们需要首先检查table[i+1][j-1], 如果table[i+1][j-1]是true的, 并且str[i]与str[j]是相同的字符, 我们就记录table[i][j]为true。否则table[i][j]就为false。 更详细的分析见下面:

如果substring(i, j)是palindromic的, 那么substring(i+1, j-1)也是palindromic的。

table[i][j]是palindromic的, 那么几位true。 otherwise, 记为0。

当j - i 比较小的时候, 即为0或者为1, 那么我们很容易知道:

table[i][i] = true,  table[i][i+1] = (S[i] == S[j]), 这是base case。

 另外, 还有table[i][j] = table[i + 1] [j - 1] && S[i]==S[j]。

table[i][i]如下:


table[i][i+1]如下:


table[i][i+2], table[i][i+3]等等如下:


程序如下:

#include <string>
#include <cstring>
#include <iostream>


using namespace std;


string longestPalindromeDP(string s) {
  int n = s.length();
  int longestBegin = 0;
  int maxLen = 1;
  bool table[1000][1000];
  memset(table, 0, sizeof(table[0][0]) * 1000 * 1000);
  // for substring of length 1 are palindrome
  for (int i = 0; i < n; i++) {
    table[i][i] = true;
  }
  //check for substring of length 2
  for (int i = 0; i < n-1; i++) {
    if (s[i] == s[i+1]) {
      table[i][i+1] = true;
      longestBegin = i;
      maxLen = 2;
    }
  }
  // check for lengths greater than 2
  // len is the length of substring
  for (int len = 3; len <= n; len++) {
    // fix the starting index
    for (int i = 0; i < n-len+1; i++) {
      int j = i+len-1;
       // checking for sub-string from ith index to
       // jth index iff str[i+1] to str[j-1] is a
       // palindrome
      if (s[i] == s[j] && table[i+1][j-1]) {
        table[i][j] = true;
        /*if(len > maxlen) { // 总是成立, 所以可以去掉这个条件
            longestBegin = i;
            maxLen = len;
        } */
        longestBegin = i;
        maxLen = len;
      }
    }
  }
  return s.substr(longestBegin, maxLen);
}
int main() {
    string s = "forgeeksskeegfor";


    cout << longestPalindromeDP(s) << endl;


    return 0;
}





运行结果如下:



分析: 时间复杂度: O(n^2)

           辅助空间复杂度: O(n^2) (其实还可以改进的, 使得改进后的空间复杂度为O(n))


(方法三)Manacher 算法, 时间复杂度为O(n)。 

虽然很屌, 太耗时间了。 有时间在研究。 you are not expected to think such a good algorithms。



(方法四): (利用最长公共子序列求解) LPS 就是str和str反转过来的LCS, 我们只需要找出这两个字符串的LCS即可。 这又归结为动态规划的内容了。 


(方法五): 观察到一个palindrome 是关于center 成镜像的。 例如 ab|ba, a b a, 注意palindrome为偶数个字符的时候,中心在两个字符之间, 当palindrome的字符个数为奇数的时候, 中心是在最中间的那个字符。 

对于一个具有N个字符的字符串, 共有2N - 1 个centers。 因为两个字符之间也可能是center,字符本身也可能是center, 所以是2N - 1。 所以我们只需要对每个中心进行expand的方式查找palindrome, 每个中心需要花费O(N), 所以这个方法的时间复杂度是O(N^2)。 

string expandAroundCenter(string s, int c1, int c2) {
  int l = c1, r = c2;
  int n = s.length();
  while (l >= 0 && r <= n-1 && s[l] == s[r]) {
    l--;
    r++;
  }
  return s.substr(l+1, r-l-1);
}
 
string longestPalindromeSimple(string s) {
  int n = s.length();
  if (n == 0) return "";
  string longest = s.substr(0, 1);  // a single char itself is a palindrome
  for (int i = 0; i < n-1; i++) {
    string p1 = expandAroundCenter(s, i, i);
    if (p1.length() > longest.length())
      longest = p1;
 
    string p2 = expandAroundCenter(s, i, i+1);
    if (p2.length() > longest.length())
      longest = p2;
  }
  return longest;
}

   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值