leetcode-5-最长回文子串

最长回文子串

1.题目描述

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

示例 1:

arduino 复制代码
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例 2:

ini 复制代码
输入: s = "cbbd"
输出: "bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

2.解决方案

1.暴力解法

  1. 思路
  • 枚举字符串 s 的所有子串,对于每个子串,检查它是否是回文串。
  • 从长度为 1 的子串开始,逐渐增加子串长度,记录下最长的回文子串。
  1. 代码实现
ts 复制代码
function longestPalindromeBruteForce(s: string): string {
    let maxLength = 0;
    let result = '';
    const n = s.length;
    for (let i = 0; i < n; i++) {
        for (let j = i; j < n; j++) {
            let isPalindrome = true;
            for (let k = 0; k < (j - i + 1) / 2; k++) {
                if (s[i + k]!== s[j - k]) {
                    isPalindrome = false;
                    break;
                }
            }
            if (isPalindrome && (j - i + 1) > maxLength) {
                maxLength = j - i + 1;
                result = s.slice(i, j + 1);
            }
        }
    }
    return result;
}
  1. 分析
  • 时间复杂度 :(O(n^3))。外层循环遍历子串的起始位置 i,时间复杂度为 (O(n));中层循环遍历子串的结束位置 j,时间复杂度为 (O(n));内层循环检查子串是否为回文,时间复杂度为 (O(n))。所以总的时间复杂度为 (O(n \times n \times n))。
  • 空间复杂度:(O(1)),除了存储结果的字符串外,只使用了常数级别的额外空间。
  1. 缺点:时间复杂度极高,对于较长的字符串,运行效率极低,会导致超时。

2.中心扩展算法

  1. 思路
  • 回文串的特点是关于中心对称,所以可以以每个字符和相邻字符间隙为中心,向两边扩展,检查扩展出的子串是否为回文。
  • 对于长度为 n 的字符串,有 2n - 1 个可能的中心(n 个字符作为单字符中心,n - 1 个相邻字符间隙作为双字符中心)。
  1. 代码实现
ts 复制代码
function longestPalindrome(s: string): string {
    let start = 0;
    let maxLength = 0;
    const n = s.length;
    for (let i = 0; i < n; i++) {
        // 以单个字符为中心扩展
        let left1 = i;
        let right1 = i;
        while (left1 >= 0 && right1 < n && s[left1] === s[right1]) {
            if (right1 - left1 + 1 > maxLength) {
                maxLength = right1 - left1 + 1;
                start = left1;
            }
            left1--;
            right1++;
        }
        // 以两个相邻字符为中心扩展
        let left2 = i;
        let right2 = i + 1;
        while (left2 >= 0 && right2 < n && s[left2] === s[right2]) {
            if (right2 - left2 + 1 > maxLength) {
                maxLength = right2 - left2 + 1;
                start = left2;
            }
            left2--;
            right2++;
        }
    }
    return s.slice(start, start + maxLength);
}
  1. 分析
  • 时间复杂度 :(O(n^2))。对于每个可能的中心,最多需要扩展 n 次,而总共有 2n - 1 个中心,所以时间复杂度为 (O(n \times n))。
  • 空间复杂度:(O(1)),只使用了常数级别的额外空间。
  1. 优点:相比暴力解法,时间复杂度有所降低,在实际应用中效率更高。

3.Manacher 算法

  1. 思路
  • Manacher 算法通过对字符串进行预处理,将奇数长度和偶数长度的回文串统一处理。
  • 它利用已经计算出的回文子串信息,避免了重复计算,从而将时间复杂度优化到 (O(n))。
  • 具体来说,算法使用一个数组 p 记录以每个字符为中心的回文半径,通过巧妙的计算和更新,快速找到最长回文子串。
  1. 代码实现
ts 复制代码
function longestPalindromeManacher(s: string): string {
    // 预处理字符串
    const newS = '#';
    for (const char of s) {
        newS += char + '#';
    }
    const n = newS.length;
    const p: number[] = new Array(n).fill(0);
    let center = 0;
    let right = 0;
    for (let i = 0; i < n; i++) {
        let iMirror = 2 * center - i;
        if (right > i) {
            p[i] = Math.min(right - i, p[iMirror]);
        } else {
            p[i] = 0;
        }
        // 尝试扩展
        while (i + (1 + p[i]) < n && i - (1 + p[i]) >= 0 && newS[i + (1 + p[i])] === newS[i - (1 + p[i])]) {
            p[i]++;
        }
        // 更新中心和右边界
        if (i + p[i] > right) {
            center = i;
            right = i + p[i];
        }
    }
    let maxLen = 0;
    let maxCenter = 0;
    for (let i = 0; i < n; i++) {
        if (p[i] > maxLen) {
            maxLen = p[i];
            maxCenter = i;
        }
    }
    const start = (maxCenter - maxLen) / 2;
    return s.slice(start, start + maxLen);
}
  1. 分析
  • 时间复杂度:(O(n))。虽然代码中有多层循环,但由于巧妙地利用了已有的回文信息,每个字符最多被访问常数次,所以时间复杂度为 (O(n))。
  • 空间复杂度 :(O(n)),需要一个数组 p 来记录回文半径。
  1. 优点:时间复杂度最优,在处理非常长的字符串时,性能远远优于暴力解法和中心扩展算法。

4.最优解及原因

  1. 最优解:Manacher 算法是最优解。
  2. 原因:当字符串长度较大时,时间复杂度是衡量算法优劣的关键指标。Manacher 算法将时间复杂度优化到了线性的 (O(n)),相比暴力解法的 (O(n^3)) 和中心扩展算法的 (O(n^2)),在处理大规模数据时效率有显著提升。虽然它需要 (O(n)) 的额外空间,但对于追求高效的场景,这种以空间换时间的方式是值得的。

3.拓展和题目变形

拓展

  • 找到所有最长回文子串。

思路

  • 在 Manacher 算法的基础上,记录所有达到最大回文半径的中心位置,然后根据这些位置还原出所有最长回文子串。

代码实现

ts 复制代码
function findAllLongestPalindromes(s: string): string[] {
    const newS = '#';
    for (const char of s) {
        newS += char + '#';
    }
    const n = newS.length;
    const p: number[] = new Array(n).fill(0);
    let center = 0;
    let right = 0;
    for (let i = 0; i < n; i++) {
        let iMirror = 2 * center - i;
        if (right > i) {
            p[i] = Math.min(right - i, p[iMirror]);
        } else {
            p[i] = 0;
        }
        while (i + (1 + p[i]) < n && i - (1 + p[i]) >= 0 && newS[i + (1 + p[i])] === newS[i - (1 + p[i])]) {
            p[i]++;
        }
        if (i + p[i] > right) {
            center = i;
            right = i + p[i];
        }
    }
    let maxLen = 0;
    const maxCenters: number[] = [];
    for (let i = 0; i < n; i++) {
        if (p[i] > maxLen) {
            maxLen = p[i];
            maxCenters.length = 0;
            maxCenters.push(i);
        } else if (p[i] === maxLen) {
            maxCenters.push(i);
        }
    }
    const results: string[] = [];
    for (const maxCenter of maxCenters) {
        const start = (maxCenter - maxLen) / 2;
        results.push(s.slice(start, start + maxLen));
    }
    return results;
}

题目变形

  • 给定一个字符串 s 和一个整数 k,找到长度至少为 k 的最长回文子串。

思路

  • 可以在中心扩展算法或 Manacher 算法的基础上进行修改。在扩展或计算回文半径时,当找到的回文子串长度达到或超过 k 时,记录下来并继续寻找更长的满足条件的回文子串。

代码实现(基于中心扩展算法)

ts 复制代码
function longestPalindromeWithMinLength(s: string, k: number): string {
    let start = 0;
    let maxLength = 0;
    const n = s.length;
    for (let i = 0; i < n; i++) {
        let left1 = i;
        let right1 = i;
        while (left1 >= 0 && right1 < n && s[left1] === s[right1]) {
            if (right1 - left1 + 1 >= k && right1 - left1 + 1 > maxLength) {
                maxLength = right1 - left1 + 1;
                start = left1;
            }
            left1--;
            right1++;
        }
        let left2 = i;
        let right2 = i + 1;
        while (left2 >= 0 && right2 < n && s[left2] === s[right2]) {
            if (right2 - left2 + 1 >= k && right2 - left2 + 1 > maxLength) {
                maxLength = right2 - left2 + 1;
                start = left2;
            }
            left2--;
            right2++;
        }
    }
    return maxLength >= k? s.slice(start, start + maxLength) : '';
}
相关推荐
茉莉玫瑰花茶4 小时前
算法 --- 分治(快排)
算法
闪电麦坤955 小时前
数据结构:哈希(Hashing)
数据结构·算法·哈希算法
l1t5 小时前
利用美团longcat.ai编写的C语言支持指定压缩算法通用ZIP压缩程序
c语言·开发语言·人工智能·算法·zip·压缩
hansang_IR5 小时前
【线性代数基础 | 那忘算9】基尔霍夫(拉普拉斯)矩阵 & 矩阵—树定理证明 [详细推导]
c++·笔记·线性代数·算法·矩阵·矩阵树定理·基尔霍夫矩阵
lingchen19065 小时前
MATLAB矩阵及其运算(三)矩阵的创建
算法·matlab·矩阵
野犬寒鸦6 小时前
力扣hot100:矩阵置零(73)(原地算法)
java·数据结构·后端·算法
定栓6 小时前
vue3入门- script setup详解下
前端·vue.js·typescript
lypzcgf8 小时前
Coze源码分析-资源库-创建提示词-前端源码
前端·人工智能·typescript·系统架构·开源软件·react·安全架构
Dream it possible!8 小时前
LeetCode 面试经典 150_矩阵_有效的数独(34_36_C++_中等)(额外数组)
leetcode·面试·矩阵