[LeetCode]Longest Palindromic Substring

题目来源:

https://leetcode.com/problems/longest-palindromic-substring/description/

这道题是很经典的一道题目,笔试中经常会考到,需要认真理解并且懂得如何实现对应的算法。

题目是要求找到一个字符串中最长的回文子串,回文串是什么意思呢?

举个例子如下:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.

就是能够关于中心对称的一个字符串,hannah也是属于一个回文串。

好了,现在我们来看实现,首先一种暴力的方式,我们可以选择直接遍历字符串,然后得到最终的结果。这种实现方式的话需要我们遍历三次字符串,需要使用3个for循环来完成:

class Solution {
public:
    string longestPalindrome(string s) {
        // 第一层循环是遍历整个字符串
        for (int size = s.length(); size > 0; size --){
              for (int low = 0, high = low+size-1; high < s.length(); low ++,high ++){
                if (checkPalindrome(s, low, high)){
                    return s.substr(low, high - low + 1);
                }
            }
        }
        return s.substr(0, 1);
    }
    // 判断一个字符串从low到high时是否是回文的
    int checkPalindrome(string s, int low, int high){
        while(low <= high){
            if(s[low] == s[high]){
                low ++; high --;
            } else {
                return false;
            }
        }
        return true;
    }
};

另外一种算法的话就是比较著名的mancher算法,很巧妙的一个算法,首先这个算法需要对输入的字符串进行一个转换,将所有的字符串转变成一个奇数长度的字符串,那么这个转换是如何做的呢,很简单,就是将原始字符串的中心插入一个特殊字符,例子如下:
a b a b c b a d
# a # b # a # b # c # b # a # d #

这里选择了#号作为特殊符号进行转化,可以看到通过这种方式,我们可以将所有的字符串转换成奇数长度的字符串,是一个很巧妙的做法,上面的例子是偶数的例子,奇数的例子大家可以自己写一下,也能够实现一样的效果。

然后完成了这个之后,我们再来看一个比较有意思的数组,
str: # a # b # a # b # c # b # a # d #
p[i]: 1 2 1 3 1 3 1 2 1 2 1 2 1 2 1 2 1

这个数组什么意思呢?就是这个处理过的新的字符串计算每个字符的最长回文串的长度,仔细观察这个数组,我们会发现,处理过的字符串包含原始实际数据的对应p[i]数组的回文串长度减去1的就是实际原来字符串的回文串长度,这个规律很神奇。我们就可以利用这个来得到我们原始数组的回文串长度。

那么现在的问题是我们该如何得到这个数组p[i],mancher算法中总结了这个数组的规律,我们来看看它的实现是如何利用这个数组的特点的。

首先我们需要定义几个变量:
1、i :这个变量是我们当前要求的这个数组的值
2、id :已知右边界最大回文子串的中心
3、mx :mx = id + p[id],这个表示的是现在能够达到的最大的右边界
4、j :j = id - (i - id)= 2id -i,这个表示的以id为中心,i的对称点

然后我们需要两个图来解释这几个变量的作用:

第一张图片,这里的id就是我们已经计算得到的p数组最大的数,也就是上面所说的最大回文子串的中心,这个是我们要求的是i这个位置的值是多少,我们可以得到 i 的对称点 j,这个时候以 j 为中心的回文子串的长度是位于id和mx对称点之间的,因为在id的mx到mx对称点之前是回文的,所以我们可以直接得到 i 的位置的回文子串就是等于 j 位置的数值,这个应该是可以理解的。也就是说在这张图的情况下,我们得到的是 p[i] = p[j] 的。
这里写图片描述

下面这张图的话就是另外一种情况,j 位置的回文半径大于了mx对称点 和 j 之间的距离,那么根据id对称的回文子串,我们可以得到这个时候 i 位置的回文串,至少是 i 到mx之间的大小,超出这部分,我们无法判断,所以在这张图中,我们可以得到这样的式子,p[i] = mx - i
这里写图片描述

不知道大家到这里能不能明白上面两张图的意思,我们要明确的一点是,在实质意义上我们是通过现有知道的p数组的内容,来求出数组其他位置的数值,这样一步一步往前走,直到得到整个数组的值。

到了这里我们就可以开始写代码了,现在应该看这个代码就会比较好理解了吧:

int p[1000], mx = 0, id = 0;
memset(p, 0, sizeof(p));
for (i = 1; s[i] != '\0'; i++) {
    p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
    while (s[i + p[i]] == s[i - p[i]]) p[i]++;
    if (i + p[i] > mx) {
        mx = i + p[i];
        id = i;
    }
}

OK!那我们了解了这个算法之后,我们解出这道题就相对比较容易了!

class Solution {
public:
    string longestPalindrome(string s) {
        string temp;  
        int p[3000] = {0};
        int mx = 0;
        int i = 0;
        int id = 0;
        int max_i = 0;
        int len = s.length();

        // initial the string
        temp.push_back('$');
        temp.push_back('#');
        for(i = 0; i < len; i++)
        {
            temp.push_back(s[i]);
            temp.push_back('#');
        }

        // mancher algorithm
        for (i = 0; i < temp.length(); i ++){
            p[i] = (mx > i) ? min(p[2*id - i], mx - i) : 1;
            while(temp[i + p[i]] == temp[i - p[i]]) p[i] ++;
            if (mx < i + p[i]){
                mx = i + p[i];
                id = i;
                if (p[i] > p[max_i])
                    max_i = i;
            }
        }

        int right = (max_i - 1)/2 - (p[max_i] - 1)/2;

        return s.substr(right, p[max_i] -1);

    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值