求最长回文串的长度

最长回文串

字符串abcbada最长的回文串为abcba,最长回文串保证首尾字符相同,并且去除首尾后的子串也是回文串,如bcb。
根据这个规律,ab就不是回文串因为首尾不同。abcbada也不是回文串,因为即使首尾相同,其子串bcbad不是回文串,所以abcbada也不是回文串。

动态规划

可以通过动态规划解决字符串的最大回文串的长度问题,其根本思路是依次列举出长度为1~n的回文串,最后返回最大长度n即可。
动态规划的思路在于找出推导公式:
1 首尾元素不同,则不是回文串。
2 首尾元素相同,且去除首尾元素后得子串仍是回文串,则该串为回文串。
假设一个字符串str,我们将上述公式写成伪代码

1  str[begin] != str[end] 则str不是回文串
2  str[begin] == str[end], 且str[begin+1:end-1]是回文串,则str是回文串。

我们用二维数组dp[i][j]表示下标从i到j的字符串是否为回文串,如果是回文串则dp[i][j] == 1,否则dp[i][j] == 0
所以一个回文串应该满足如下条件

dp[i+1][j-1] ==1 && str[i] == str[j]

长度为1时,从i到i的字符串默认为回文串,dp[i][i] = 1
长度为2时,从i到i+1的字符串,判断str[i]==str[i+1] --> dp[i][i+1]=1
长度为3时,从i到i+2的字符串, 判断str[i] == str[i+2] && dp[i+1][i+1] ==1 -> dp[i][i+2] = 1
长度为4时,从i到i+3的字符串,判断str[i] == str[i+3] && dp[i+1][i+2] ==1 -> dp[i][i+3] = 1
.....
推导出dp的规律后开始写代码,将所有可能dp计算出来即可

int max_palindrome(string str, string &palindstr)
{
    //初始化二维vector
    vector<vector<int>> dp(str.length(), vector<int>(str.length(), 0));
    int maxpalind = 0;
    //先进行一次遍历统计长度为2和长度为1的dp
    //为以后递推长度为n的dp做准备
    for (size_t i = 0; i < str.length(); i++)
    {
        dp[i][i] = 1;
        if (i + 1 >= str.length())
        {
            break;
        }

        if (str[i] == str[i + 1])
        {
            dp[i][i + 1] = 1;
            maxpalind = 2;
        }
    }
    //"abcdcba"
    //外层循环控制长度
    for (int len = 3; len <= str.length(); len++)
    {
        //内层循环控制起始位置
        for (int i = 0; i + len - 1 < str.length(); i++)
        {
            //首尾相同并且去掉首尾后子串仍是回文串
            if (str[i] == str[i + len - 1] && dp[i + 1][i + len - 2] == 1)
            {
                //更新最大长度
                maxpalind = len;
                //更新dp标记,标记i~i+len-1为回文串
                dp[i][i + len - 1] = 1;
                palindstr = str.substr(i, i + len - 1);
            }
        }
    }
    return maxpalind;
}

依次从长度3计算到字符串长度,最后更新的maxpalind为最大长度,回文串可能不止一个,这里返回最后一个。
在main函数中测试

int main(){
    cout << "Dynamic programming ...." << endl;
    string str = "abcdcb";
    string temp = "";
    cout << "str is " << str << endl;
    int maxlen = max_palindrome(str, temp);
    cout << "max palindrome is " << temp << " size is " << maxlen << endl;
}

程序输出

Dynamic programming ....
str is abcdcb
max palindrome is bcdcb size is 5

中心扩展法

用中心扩展法同样可以解决字符串回文问题,选在每个元素,将其设置为中心,如果满足回文串,则依次向左向右扩充,直到到达边界或者不满足回文串条件为止。
如下图
https://cdn.llfc.club/1641969894.jpg
依次从索引为0的节点为中心,直到索引为4的节点,当以某个节点为中心时,判断他的左边节点和右边节点是否相等,如果相等则左节点向左,右节点向右,直到遇到左右节点不相等或者左右节点为边界节点结束。
举例:
当我们找到索引为2的节点c,判断他的左节点为b,右节点也为b,则左节点左移此时指向a,右节点右移指向a,此时左右节点已到达边界,就找到了最大的回文串abcba。
上述思路有一个问题就是面对如下情况,找到的最大回文串是不正确的。
https://cdn.llfc.club/1641970519.jpg
按照上述办法无法找到最大回文串,而此回文串是存在的为abccba, 问题出在偶数节点对称的情况下,我们以索引为2的节点为中心,左节点为b,右节点为c无法找到回文串。解决的方案是将索引2设为左节点,索引3设为右节点就可以找到回文串。
所以扩展的方案修改为上述两种算法的并集
1 以单节点为中心,判断左右节点对称
2 以双节点为中心,判断左右节点对称
先实现根据左右节点相等,则左节点左移,右节点右移的逻辑

int cal_maxlen(string str, int left, int right)
{
    while (left >= 0 && right < str.length() && str[left] == str[right])
    {
        left--;
        right++;
    }
    //因为边界以及左右值不等情况下,此时要-1
    // 假设字符串为abcbd 此时left 为0,right 为4=> 4-0-1=3
    return right - left - 1;
}

上述代码根据左右节点差值-1计算出回文串长度。
接下来实现以节点为中心遍历展开的逻辑

int center_expend(string str, string &palindstr)
{
    int maxpalind = 0;
    //从0开始遍历,直到字符串结尾
    for (int i = 0; i < str.length() - 1; i++)
    {
        //以单节点为中心扩展
        auto len2 = cal_maxlen(str, i, i);
        //以双节点为中心扩展
        auto len1 = cal_maxlen(str, i, i + 1);
        auto maxlen = 0;
        len2 > len1 ? maxlen = len2 : maxlen = len1;
        if (maxlen > maxpalind)
        {
            maxpalind = maxlen;
            //此处计算左右节点的位置
            //根据总长度maxlen折半找到起始位置
            // 假设字符串为abccba, i为2,maxlen为6
            //如果字符串为abcba,i为2,maxlen为5,下面规则同样适用
            auto start = i - (maxlen - 1) / 2;
            auto end = i + maxlen / 2;
            palindstr = str.substr(start, end+1);
        }
    }

    return maxpalind;
}

接下来在主函数中做测试

int main(){
    cout << "Center expend ...." << endl;
    string str2 = "abcddcbams";
    string temp2 = "";
    int maxlen2 = center_expend(str2, temp2);
    cout << "str2 is " << str2 << endl;
    cout << "max palindrome is " << temp2 << " size is " << maxlen2 << endl;
}

程序输出

Center expend ....
str2 is abcddcbams
max palindrome is abcddcba size is 8

总结

解决字符串回文的算法还有马拉车算法,就是优先通过遍历在每个字符前后插入'#',再执行中心扩展算法达到o(n)复杂度。这里不做赘述,个人认为善于通过动态规划和中心扩展算法解决回文问题即可。

posted @ 2022-01-12 15:47  恋恋风辰  阅读(101)  评论(0编辑  收藏  举报