1.题目


Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1: Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2: Input: "cbbd" Output: "bb".

题目的大意是求取给定字符串中最长的回文子串(palindromic substring),顺带限定了字符串的最大长度(1000),简化编码不用malloc了,不过无关紧要.

2.思路


最原始的做法,暴力搜索:找出字符串s的所有可能子串,判断每个子串是否回文串.其思路比较简单,直接贴代码:

int
is_palindrome(char *s, int begin, int end) {
while (begin <= end && s[begin] == s[end])
++begin, --end;
if (begin < end)
return ;
else
return ;
} char *longestPalindrome(char *s) {
int i, step;
int max, begin; if (strlen(s) < )
return s; max = ;
begin = ;
for (i = ; i < strlen(s); ++i) {
for (step = ; step <= strlen(s) - i - ; ++step) {
if (is_palindrome(s, i, i + step)) {
if (step > max) {
begin = i;
max = step;
}
}
}
}
s[begin + max + ] = ;
return &s[begin];
}

对于一个长度为n的字符串,其子串(不含单个字符的情形)共有n*(n-1)/2,因为一组下标(begin,end)表达了一个子串,从0,1,2,...,n-1个数中选出两个数,就是个排列组合问题.对于每个子串,判断其是否回文串,时间复杂度Ο(n),所以暴力破解的时间复杂度是Ο(n3).

很显然暴力搜索的方法是可能优化的.如果在求解一个问题的时候,所有的已知条件都使用了,那么这个解法应该不太可能优化了,如果还存在未使用的条件,那说明解法还存在优化的可能.有些时候这些条件是很明显的,而有些时候却隐藏的很深,甚至需要复杂的数学推导(个人的一点心得,有不对的,轻拍).回到正题,那么在暴力破解法中有哪些条件是我们还未使用的呢?例如知道"bab"是回文串,在判断"xbabx"时还有必要遍历吗?显然只需要比较两端的字符是否相等即可.

3.空间换时间


为了判断子串是否是回文串,我们可以利用已有的回文串信息简化操作,这意味着我们需要将已经找到的回文串信息记录下来.这是一种典型的空间换时间的做法.思路有了,如何实现呢?

对于一个回文子串,有三个信息:起始下标,结束下标,长度(或者认为是两个信息,长度可以用起止坐标计算).设L(i,j)表示一个回文子串的长度,S[i]表示下标i处的字符,根据我们的分析有以下结论:

L(i,j) = L(i+1,j-1) + 2; if S[i] == S[j]

特别地,L(i,i) = 1,  L(i,i+1) = 2 if S[i] == S[i+1]. 有了这个结论,我们就可以用一个二维数组P[i][j]保存已知的回文子串信息来提高效率(你可能习惯称这种方法为动态规划(dynamic programming)).

/* 采用动态规划方式求解最长回文子串.
* 设P(i,j)为回文子串的长度,则有如下关系:
* P(i,j) = P(i+1,j-1) + 2; if S[i] == S[j]
* 特别地, P(i,i) = 1; P(i,i+1) = 2 if S[i] == S[i+1]
*/
char *
longestPalindrome(char *s) {
int P[][] = {};
int i, j, maxi, maxj, max;
size_t len; len = strlen(s);
if (len < )
return s; max = ;
maxi = maxj = ;
for (j = ; j < len; ++j) {
for (i = ; i <= j; ++i) {
if (i == j)
P[i][j] = ;
else if (i + == j && s[i] == s[j])
P[i][j] = ;
else if (i + < j && P[i+][j-] > && s[i] == s[j])
P[i][j] = P[i + ][j - ] + ; if (P[i][j] > max) {
max = P[i][j];
maxi = i;
maxj = j;
}
}
}
s[maxj + ] = ;
return &s[maxi];
}

上述代码可以将更新max(最长回文子串的长度)的操作放在if语句分支里面减少不必要的判断,这里为了代码简洁没有这么做.这种解法相比于暴力破解法的优势是提高了判断一个子串是否回文串的效率(Ο(n)变为Ο(1)),故整个算法的时间复杂度是Ο(n2).

是否到此为止了呢?是否还有优化的可能或者说是否还存在未使用的已知条件?当然是有的(不然就不会有这篇博客了XD).所谓回文串是反转后和原字符串相同的字符串(等等,这个条件我们前面两种方法不都用到了吗?确实是用了,但并未有效或者说完全利用.),对于每一个回文子串S(i,j),可以假想其有一个中点下标是c,有一个半径r=L(i,j)/2,中点两边距中点距离相等的字符必然相等,这是一种对称性,利用这个性质我们能简化下标在(c, c+r)范围内的每个点的最大回文串长度的计算吗?答案是YES!

4.Manacher算法


关于这个算法的详细说明我就不班门弄斧了,附上两个链接,一个e文,一个中文,图文并茂,个人认为讲的都比较清楚.

Longest Palindromic Substring

最长回文子串

最后附上自己的实现作为结束:

/* Manacher's Algorithm
* 关键思想是利用回文字符串的对称性,为了简化处理字符串长度的奇偶问题,
* 对原字符串进行了预处理.对于处理后的字符串为了简化边界处理,在起始
* 位置设置了哨兵,结束位置采用'\0'作为哨兵.
*/ #define min(x, y) ((x) > (y) ? (y) : (x)) char *
preProcess(char *s) {
size_t len, i;
char *ret; len = strlen(s);
ret = malloc(( * len + ) * sizeof(char));
memset(ret, '#', * len + );
/* 设置哨兵,简化计算P时的越界检查,设置的哨兵不能等于原字符串中
* 任意一个字符.
*/
ret[] = '^';
for (i = ; i <= len; ++i)
ret[ * i] = s[i - ];
ret[ * len + ] = ; return ret;
} char *
longestPalindrome(char *s) {
int *P;
size_t len, max, center, right, i, mirror;
char *ptr; len = strlen(s);
if (len < )
return s; ptr = preProcess(s);
P = calloc( * len + , sizeof(int)); /* 比处理后的字符串少一个结束符'\0' */ center = ; /* 回文字符串的中点 */
right = ; /* 回文字符串的右边界 */
for (i = ; i < * len + ; ++i) {
mirror = * center - i; /* center - (i - center) */ /* 利用回文字符串的对称性快速计算i处的回文字符串长度:
* i处关于中点center的对称点mirror处的回文字符串长度P[mirror]小于i到边界right的距离,
* 则P[i] = P[mirror],否则P[i] = right - i.
*/
P[i] = (right > i) ? min(right - i, P[mirror]) : ; /* 往右搜索扩展回文串,因为ptr[0]设置了哨兵故无需担心数组越界 */
while (ptr[i + P[i] + ] == ptr[i - P[i] - ])
++P[i]; /* 更新回文串的中点和右边界 */
if (i + P[i] > right) {
center = i;
right = i + P[i];
}
}
/* 返回最长的回文串 */
max = ;
for (i = ; i < * len + ; ++i) {
if (P[i] > max) {
max = P[i];
center = i;
}
}
free(P);
free(ptr);
s[(center + max) / ] = ;
return &s[(center - max) / ];
}

Longest Palindrome 最长回文串问题的更多相关文章

  1. [LeetCode] Longest Palindrome 最长回文串

    Given a string which consists of lowercase or uppercase letters, find the length of the longest pali ...

  2. 409 Longest Palindrome 最长回文串

    给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串.在构造过程中,请注意区分大小写.比如 "Aa" 不能当做一个回文字符串.注意:假设字符串的长度不会超过 ...

  3. [LeetCode] 409. Longest Palindrome 最长回文

    Given a string which consists of lowercase or uppercase letters, find the length of the longest pali ...

  4. LeetCode409Longest Palindrome最长回文串

    给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串. 在构造过程中,请注意区分大小写.比如 "Aa" 不能当做一个回文字符串. 注意: 假设字符串的长度不 ...

  5. POJ----(3974 )Palindrome [最长回文串]

    Time Limit: 15000MS   Memory Limit: 65536K Total Submissions: 5121   Accepted: 1834 Description Andy ...

  6. 字符串的最长回文串:Manacher’s Algorithm

    题目链接:Longest Palindromic Substring 1. 问题描述 Given a string S, find the longest palindromic substring ...

  7. Leetcode0005--Longest Palindromic Substring 最长回文串

    [转载请注明]http://www.cnblogs.com/igoslly/p/8726771.html 来看一下题目: Given a string s, find the longest pali ...

  8. Manacher(输出最长回文串及下标)

    http://acm.hdu.edu.cn/showproblem.php?pid=3294 Girls' research Time Limit: 3000/1000 MS (Java/Others ...

  9. Java实现 LeetCode 409 最长回文串

    409. 最长回文串 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串. 在构造过程中,请注意区分大小写.比如 "Aa" 不能当做一个回文字符串. 注意 ...

随机推荐

  1. python随机数学习笔记

    #coding:utf-8 import random # random.randint(1,10)产生1,10的随机整数 for i in range(1,5): ranint = random.r ...

  2. 利用Pluggable Protocol实现浏览器打开本地应用程序

    https://www.cnblogs.com/liushaofeng89/archive/2016/05/03/5432770.html

  3. [ZZ] 用matlab绘制箭头

    用matlab绘制箭头 http://npfeng900.blog.163.com/blog/static/14456108201221922944998/ 用matlab绘制箭头1 用matlab绘 ...

  4. Vue 给对象添加属性

    坑真多,没想到很多小细节都 改了,我添加个属性都 折腾了半天才看明白原因 Vue.set(row,"isEdit",false);   //给row对象新增一个isEdit的属性.

  5. win10 家庭版 升级 win10企业版

    更改秘钥 我的电脑(右键)->属性-> 更改产品秘钥 -> 96YNV-9X4RP-2YYKB-RMQH4-6Q72D->重启系统 如果秘钥过期了,就百度按时间搜索,总有一个是 ...

  6. MVC Action 返回类型

    https://www.cnblogs.com/xielong/p/5940535.html https://blog.csdn.net/WuLex/article/details/79008515 ...

  7. vim汇总

    跳到100行 100gg :100    

  8. 树莓派 nfs server安装

    安装服务 sudo  apt-get install  portmap sudo  apt-get install  nfs-kernel-server 配置: sudo nano /etc/expo ...

  9. 使用Navicat连接阿里云mysql报错10061

    1.添加一个远程访问账号admin mysql> use mysql; mysql> GRANT ALL ON *.* TO 账户@'%' IDENTIFIED BY '密码' WITH ...

  10. linux: 用户组, 文件权限详解

    一.用户组 linux中每个用户必须属于一个组,不能独立于组外. 每个文件有所有者.所在组.其他组的概念 --所有者 一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者 用ls ‐ahl命 ...