LeetCode_5_最长回文子串

链接

LeetCode_5_最长回文子串

题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

思路

思路一、动态规划

假设字符串("babab")为s, 它的长度为n
dp是大小为 n * n 的二维数组, dp[i][j] 表示s[i,j]是否为回文串, 存储true, false

如何求出dp[i][j]的值? 分两种情况
① 如果s[i,j]的长度(j - i + 1) <= 2时:
     如果s[i]等于s[j], 那么s[i,j]是回文串, 所以dp[i][j] = s[i] == s[j]
② 如果s[i,j]的长度(j - i + 1) > 2时:
    如果s[i+1, j-1]是回文串, 并且s[i]等于s[j], 那么s[i,j]是回文串, 所以dp[i][j] = dp[i+1, j-1] && ( s[i] == s[j-1] )

时间复杂度: O(n^2), 空间复杂度: O(n^2)

代码实现

/**
 * 1.动态规划
 * dp(i,j): 表示字符串[i,j]范围是否为回文串, 是的话存储true, 不是存储false
 * dp(0,0) = true, dp(1,1) = true,dp(2,2) = true...
 * dp(i,j) 如果s[i,j]的长度小于2时: if(s[i] == s[j]) s[i,j] = true
 *                               else   dp[i,j] = false
 *         如果s[i,j]的长度大于2时: if(dp[i+1,j-1] == true) s[i,j] = (dp[i+1,j-1] && s[i] == s[j])
 *                               else   dp[i,j] = false
 */
public String longestPalindrome(String s) {
    if (s == null) return "";
    char[] cs = s.toCharArray();
    int len = cs.length;
    if(len <= 1) return s;

    boolean[][] dp = new boolean[len][len];
    int maxLen = 1;
    int begin = 0;
    //状态转移计算
    for (int i = len-1; i >= 0; i--) {
        for (int j = i; j < len; j++) {
            int currLen = j-i+1;
            if(currLen <= 2){ //如果子串长度不超过2
                dp[i][j] = cs[i] == cs[j];
            }else{  //如果子串长度超过2
                dp[i][j] =  dp[i+1][j-1] && (cs[i] == cs[j]);
            }
            if(dp[i][j] && currLen > maxLen){
                maxLen = currLen;
                begin = i;
            }
        }
    }
    return new String(cs,begin,maxLen);
}

 

思路二、扩展回文串中心

假设字符串 (“abbaba”) 的长度为 n, 那么一共有n + (n-1) == 2n - 1 个扩展中心

时间复杂度: O(n^2),  空间复杂度: O(1)

代码实现

/**
 * 思路: 扩展中心, 以每个字符作为回文串中心向两边扩展
 */
public String longestPalindrome(String s) {
    if (s == null) return null;
    char[] cs = s.toCharArray();
    if (cs.length <= 1) return s;
    int maxLen = 1; // 最长回文子串的长度(至少是1)
    int begin = 0;  // 最长回文子串的开始索引
    /**
     * 1.除去第一个和最后一个元素,以中间这些元素作为回文子串的中心向两边扩展
     * 2.以每两个元素之间的间隙作为中心向两边扩展
     */
    for (int i = cs.length - 2; i >= 1; i--) {
        // 以字符为中心向左右扩展(倒序遍历) | 以字符左边的间隙为中心向左右扩展(正序遍历)
        int len1 = palindromeLength(cs, i - 1, i + 1);
        // 以字符右边的间隙为中心向左右扩展(倒序遍历) | 以字符为中心向左右扩展(正序遍历)
        int len2 = palindromeLength(cs, i, i + 1);
        len1 = Math.max(len1, len2);
        //更新最长回文子串的长度以及子串起始位置
        if (len1 > maxLen) {
            maxLen = len1;
            //如果回文串长度是偶数,那么说明中心在为两个元素之间的间隙; 如果是奇数, 那么中心在中间的元素;
            begin = i - ((maxLen - 1) >> 1);
        }
    }
    // 以0号字符右边的间隙为中心的最长回文子串长度是2
    if (cs[0] == cs[1] && maxLen < 2) {
        // cs[0, 1]就是最长的回文子串
        begin = 0;
        maxLen = 2;
    }
    return new String(cs, begin, maxLen);
}

/**
 * @return 从l开始向左、从r开始向右扫描,获得的最长回文子串的长度
 */
private int palindromeLength(char[] cs, int l, int r) {
    while (l >= 0 && r < cs.length && cs[l] == cs[r]) {
        l--;
        r++;
    }
    return r - l - 1;
}

 

思路三、扩展回文串中心优化

优化点: 由连续的相同字符组成的子串作为扩展中心

比如, 字符串"babbbabaa"的扩展中心有: "b", "a", "bbb", "a", "b", "aa"

核心逻辑: 
  找到右边第一个不等于s[ i ]的字符, 记为位置 r, i 左边位置记为 l
  r作为下一次的 i,  由 l 开始向左, r 开始向右扩展, 找到最长的回文子串

代码实现

/**
 * 扩展中心法优化: 由连续的相同字符组成的子串作为扩展中心
 * 字符串"babbbabaaa"的扩展中心有: "b","a","bbb","a","b","aa"
 */
public static String longestPalindrome_(String s) {
    if (s == null) return null;
    char[] cs = s.toCharArray();
    if (cs.length <= 1) return s;
    int maxLen = 1; // 最长回文子串的长度(至少是1)
    int begin = 0;  // 最长回文子串的开始索引
    int i = 0;
    while (i < cs.length) {  //从左向右遍历数组
        int l = i - 1; //暂存当前中心的左边界
        /**
         * 寻找扩展中心, 即:找到右边第一个不等于cs[i]的位置
         */
        int r = i;
        while (++r < cs.length && cs[r] == cs[i]);
        i = r; //已确认当前扩展种中心字符串的左右边界, 那么下一次遍历时,下一个扩展中心的左边界就是i

        // 从l向左,从r向右扩展
        while (l >= 0 && r < cs.length && cs[l] == cs[r]) {
            l--;
            r++;
        }

        // 扩展结束后,cs[l + 1, r)就是刚才找到的最大回文子串
        // ++l后,l就是刚才找到的最大回文子串的开始索引
        int len = r - ++l;
        if (len > maxLen) {
            maxLen = len;
            begin = l;
        }
    }
    return new String(cs, begin, maxLen);
}

 

思路四、Manacher算法

定义

中间的 # 字符可以是任意字符, 头部的^字符、尾部的$字符,必须是原字符串中不包含的字符
m[i]的含义: 
①是以cs[ i ]为扩展中心的最大回文子串的长度(不包括#字符)
  最大回文子串在原字符串中的开始索引: ( i - m[ i ] ) >> 1
②是以cs[ i ]为扩展中心的最大回文子串的右半部分或左半部分的长度(包含 # 字符)
  所以,Manacher算法的关键在于求出m数组

情景1

已知
索引l, li, c, i, r的值分别为1, 4, 6, 8, 11;  cs[ l, r ]是以c为中心的最大回文串; i, li 以 c 为中心对称, m[i]是待求项
因为:
   m[li] == 1,  i + m[li] < r
由于回文的对称性, 得出结论:
  m[i] = m[li],  即: m[i] == 1

情景2

已知: 
  m[ li ] == 3, i + m[ li ] == r
结论: 
  m[ i ]至少是m[ li ], 也就是说, 至少是3
  接下来利用扩展中心法以i为中心计算出m[ i ]
  当 i + m[i] > r 时, 更新c, r
     c = i, r = i + m[i]

情景3

已知:
  m[ li ] == 5, i + m[ li ] > r
结论:
  m[ i ]至少是r - i, 也就是说, 至少是3
  接下来利用扩展中心法以 i 为中心计算出 m[i]
  当i + m[i] > r 时, 更新 c, r
    c = i, r = i + m[i]

情景4

当 i == r 时
  直接利用扩展中心法以 i 为中心计算出m[i]
当 i + m[i] > r时, 更新c, r
  c = i, r = i + m[i]

代码实现

/**
 * 思路: Manacher算法, 最优解
 */
public String longestPalindrome(String s) {
    if (s == null) return null;
    char[] oldCs = s.toCharArray();
    if (oldCs.length <= 1) return s;

    // 预处理
    char[] cs = preprocess(oldCs);
    // 构建m数组
    int[] m = new int[cs.length];
    int c = 1, r = 1, lastIdx = m.length - 2;
    int maxLen = 0, idx = 0;
    for (int i = 2; i < lastIdx; i++) {
        if (r > i) {
            int li = (c << 1) - i;
            m[i] = (i + m[li] <= r) ? m[li] : (r - i);
        }

        // 以i为中心,向左右扩展
        while (cs[i + m[i] + 1] == cs[i - m[i] - 1]) {
            m[i]++;
        }

        // 更新c、r
        if (i + m[i] > r) {
            c = i;
            r = i + m[i];
        }

        // 找出更大的回文子串
        if (m[i] > maxLen) {
            maxLen = m[i];
            idx = i;
        }
    }
    int begin = (idx - maxLen) >> 1;
    return new String(oldCs, begin, maxLen);
}

private char[] preprocess(char[] oldCs) {
    char[] cs = new char[(oldCs.length << 1) + 3];
    cs[0] = '^';
    cs[1] = '#';
    cs[cs.length - 1] = '$';
    for (int i = 0; i < oldCs.length; i++) {
        int idx = (i + 1) << 1;
        cs[idx] = oldCs[i];
        cs[idx + 1] = '#';
    }
    return cs;
}

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文子串问题。 方法一是使用扩展中心法优化,即从左向右遍历字符串,找到连续相同字符组成的子串作为扩展中心,然后从该中心向左右扩展,找到最长的回文子串。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符串,判断子串是否是回文子串,然后得到最长回文子串。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有子串可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文子串问题的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文子串](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文子串](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5题:最长回文子串(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值