前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多种思路深度剖析经典面试题---最长回文子串

多种思路深度剖析经典面试题---最长回文子串

原创
作者头像
程序员小熊
修改2021-05-17 11:12:48
5970
修改2021-05-17 11:12:48
举报

前言

大家好,我是程序员小熊,来自大厂的程序猿。最长回文子串是面试中常考的题目,尤其是一些互联网大厂,像亚马逊、微软、脸书、字节和腾讯等考过这道题。

本文提供四种解题思路三种解法,将本题解法的时间复杂度O(n^3) 一步步降为 O(n) ,供大家参考,希望对大家有所帮助。

题目

给你一个字符串 s,找到 s 中最长的回文子串。

示例
示例

解题思路

本题要求的是最长回文子串,必须先明确两个概念。

回文串从左向右读跟从右向左读都一样的字符串。例如 “level”或者“noon”等等。

子串:原始字符串中任意个连续的字符组成的子序列。

由于题目提示 1 <= s.length <= 1000,因此设计一个 O(n^2) 的算法是合理的。因为 O(n^2) 的算法可以在 1s 内处理大约 10^4 级别的数据;并且从示例1中可以知道,如果字符串存在多个最长回文子串,只需要输出一个即可。

暴力法

以字符串 "abba" 为例子,如下动图所示。

暴力法动图
暴力法动图

要求字符串的最长回文子串,只需要先找出最长回文子串的起始位置,再求出最长回文子串的长度即可。

因此可以通过两层遍历枚举长度大于等于2且超过当前得到的最长回文子串长度的子串,再加一层判断子串是否是回文串的遍历,就可求出给定字符串的最长回文子串。

Show me the Code

java

代码语言:javascript
复制
/* 验证子串 s[left...right]是否为回文子串 */
boolean isPalindrome(char[] charArray, int left, int right) {
    while (left < right) {
        if (charArray[left] != charArray[right]) {
            return false;
        }
        left++;
        right--;
    }

    return true;
} 

String longestPalindrome(String s) {
    int len = s.length();
    /* 长度少于 2 的字符串的最长回文子串是其自身 */
    if (len < 2) {
        return s;
    }

    int maxLen = 1;   // 记录最长回文子串的长度
    int start = 0;    // 记录最长回文子串的起始位置
    char[] charArray = s.toCharArray();
    for (int i = 0; i < len - 1; i++) {
        for (int j = i + 1; j < len; j++) {
            /* 回文子串 s[i...j] 的长度超过当前最长回文子串长度 */
            if (j - i + 1 > maxLen && isPalindrome(charArray, i, j)) {
                /* 更新最长回文子串的长度 */
                start = i;
                maxLen = j - i + 1;
            }
        }
    }

    return s.substring(start, start + maxLen);
}

复杂度分析

时间复杂度:三层遍历 O(n^3)

空间复杂度:没有开辟额外空间 O(1)

动态规划

回文串具有天然状态转移性,一个长度大于 2 的回文串,去掉首尾两头之后,剩余的部分依然是回文串。

情况一:如果一个子串首尾两头的字符不相同,则该子串不是回文串。如下图示。

回文串判断1
回文串判断1

情况二:如果一个子串首尾两头的字符相同,则去掉首尾两头的字符,继续判断去掉后的子串,直至子串的首尾两头的字符不相同或子串为空。如下动图示。

回文串判断2
回文串判断2

也就是说一个子串首尾两头的字符相同去掉首尾两头的字符后剩余的子串是否是回文串决定原子串是否是回文串

状态:dp[i][j] 表示子串 s[i...j] 是否为回文子串。

状态转移方程

状态转移方程
状态转移方程

边界条件:[i + 1...j - 1]不成立(构成区间)

边界条件
边界条件

整理得:

边界条件
边界条件

即当 len(s[i...j])= 2 or 3 时,不用检查子串是否是回文串,不需要状态转移。

初始化:单个字符一定是字符串 dp[i][j] = true。

输出:状态为 true 时,记录当前最长回文子串的起始位置和长度,填完表后截取字符串。

举例 以字符串 "abcba" 为例子,如下动图示,表格中的 dp[i][j] 也可参考它左下方的值填写。

状态转移表
状态转移表

Show me the Code

java

代码语言:javascript
复制
String longestPalindrome(String s) {
    int len = s.length();
    if (len < 2) {
        return s;
    }

    int start = 0;
    int maxLen = 1;

    /* dp[i][j]:s[i...j] 是否是回文串 */
    boolean[][] dp = new boolean[len][len];
    for (int i = 0; i < len; i++) {
        dp[i][i] = true;
    }

    char[] charArray = s.toCharArray();
    for (int j = 1; j < len; j++) {
        for (int i = 0; i < j; i++) {
            if (charArray[i] != charArray[j]) {
                dp[i][j] = false;
            } else {
                if (j - i < 3) {
                    dp[i][j] = true;
                } else {
                    dp[i][j] = dp[i + 1][j - 1];
                }
            }

            /* s[i...j] 是回文串,记录当前最长回文串长度和起始位置 */
            if (dp[i][j] && j - i + 1 > maxLen) {
                maxLen = j - i + 1;
                start = i;
            }
        }
    }

    return s.substring(start, start + maxLen);
} 

相较于暴力法动态规划利用状态转移方程,快速判断一个子串是否是回文串,每一步计算都尽可能地利用之前计算的结果(保存的),空间换时间

复杂度分析

时间复杂度O(n^2)

空间复杂度O(n^2)

中心扩散法

回文串的枚举可以从两边开始,也可以从中间开始,所以可以枚举所有可能的回文子串的中心位置

由于奇/偶数长度的回文子串的中心位置不一样,所以枚举时需要考虑回文子串长度是奇数还是偶数,如下图示。

偶数回文串
偶数回文串
奇数回文串
奇数回文串

举例

以奇数长度的字符串 "abcba" 为例子,中心扩散如下动图示,

中心扩散动图
中心扩散动图

Show me the Code

C++

代码语言:javascript
复制
/* s[left] 和 s[right] 想两断扩散, 求以 s[left] 和 s[right] 为中心的最长回文串*/
string longestPalindromeHelper(string s, int left, int right) { 
    while (left >= 0 && right < s.length() && s[left] == s[right]) {
        left--;
        right++;
    }
    
    return s.substr(left + 1, right - left - 1);
}

string longestPalindrome(string s) {
    int len = s.length();
    if (len < 2) {
        return s;
    }

    string res;
    for (int i = 0; i < len; i++) {
        /* 以s[i]作奇数回文子串的的回文中心向两边扩散,得到的最长回文子串*/
        string s1 = longestPalindromeHelper(s, i, i);
        /* 以s[i]、s[i + 1] 分别作偶数回文子串的的回文中心向两边扩散,得到的最长回文子串*/
        string s2 = longestPalindromeHelper(s, i, i + 1);
        res = res.size() > s1.size() ? res : s1;
        res = res.size() > s2.size() ? res : s2;
    }

    return res;
}

复杂度分析

时间复杂度O(n^2),枚举中心位置的个数是 2(n - 1),每次向两边扩散检测试服是回文子串。

空间复杂度O(1)

Manacher 算法

通过将原始字符串进行预处理(用不在输入字符串中的字符隔开),使得奇/偶数长度的回文串的性质统一表示。该算法专门用于查找最长回文子串,其时间复杂度为 O(n)

由于绝大多数笔/面试不要求掌握次算法,所以这里就不再介绍了,感兴趣的童鞋,可以查阅相关资料进一步了解。

往期字符串相关精彩文章

动态规划经典题之最长上升子序列最终版

动态规划经典题之最长上升子序列

字符串最长子串难?滑动窗口拯救你

更多精彩

关注公众号【程序员小熊

微信公众号
微信公众号

后台回复【算法】,即可获取高清无码的经典算法电子书~

后台回复【python】,即可获取高清无码的经典 python 电子书~

后台回复【1024】,即可获取三份来自 BAT 大佬的 Leetcode 刷题手册~

为了回馈读者,本公众号不定期会有送礼活动,敬请关注~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 题目
  • 解题思路
  • 暴力法
    • Show me the Code
    • 动态规划
      • Show me the Code
      • 中心扩散法
        • Show me the Code
        • Manacher 算法
        • 往期字符串相关精彩文章
        • 更多精彩
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档