【LeetCode】解题5:Longest Palindromic Substring(多解法:动态规划+中心扩散)

LeetCode解题 5:Longest Palindromic Substring(多解法:动态规划+中心扩散)

Problem 5: Longest Palindromic Substring [Medium]

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.

Example 2:

Input: “cbbd”
Output: “bb”

来源:LeetCode

解题思路

1. DP

使用动态规划思想。维护一个长度为N的数组len,len[i]代表以 s i s_i si为结尾的最长回文子串的长度,遍历字符串计算len[i],每次计算出新的len[i]后更新一次maxLen和end。

具体思路:

  • 首先将len[0],maxLen初始化为1,end初始化为0。
  • 遍历字符串 s s s,计算len[i],计算方法为:
    a. 假设len[i-1] = m,则 s i − m , s i − m + 1 , . . . , s i − 1 s_{i-m}, s_{i-m+1}, ..., s_{i-1} sim,sim+1,...,si1为以 s i − 1 s_{i-1} si1结尾的最长回文子串。
    b. 若 s i − m − 1 = s i s_{i-m-1} = s_{i} sim1=si,则有len[i] = len[i-1] + 2。即,以 s i s_i si结尾的最长回文子串为 s i − m − 1 , s i − m , . . . , s i − 1 , s i s_{i-m-1}, s_{i-m}, ..., s_{i-1}, s_i sim1,sim,...,si1,si,比以 s i − 1 s_{i-1} si1结尾的回文子串长度增加了2。
    c. 若 s i − m − 1 ≠ s i s_{i-m-1} \neq s_{i} sim1=si或者 i − m − 1 < 0 i-m-1 < 0 im1<0,则在 s i − m , . . . , s i s_{i-m}, ..., s_i sim,...,si范围内用双指针寻找以 s i s_i si结尾的最长回文子串。(Solution DP中的函数findSym
  • 不断更新maxLen和end,更新公式为:
    { m a x L e n , e n d } = { { m a x L e n , e n d } l e n [ i ] ≤ m a x L e n ; { l e n [ i ] , i } l e n [ i ] > m a x L e n . \{maxLen, end\} = \begin{cases} \{maxLen, end\} & len[i] \leq maxLen;\\ \{len[i], i\} & len[i] > maxLen. \end{cases} {maxLen,end}={{maxLen,end}{len[i],i}len[i]maxLen;len[i]>maxLen.
  • 最后返回子串 s e n d − m a x L e n + 1 , . . . , s e n d s_{end-maxLen+1}, ..., s_{end} sendmaxLen+1,...,send

最坏情况下每次计算len[i]都需要遍历i-1次,总时长为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( n ) O(n) O(n)

运行结果:
在这里插入图片描述

2. 中心扩散法

遍历字符串 s s s,分别以 s i s_i si s i − 1 / 2 s_{i-1/2} si1/2为中心向左右扩展回文串,不断更新最大长度maxLen和子串起始位置start。

具体思路:

  • 首先将maxLen初始化为1,start初始化为0。
  • 遍历字符串 s s s,使用双指针法计算len1和len2,具体计算方法为:
    a. len1 = 以 s i − 1 / 2 s_{i-1/2} si1/2为中心的最长回文子串长度,令双指针起始位置为p = i-1,q = i,不断令p- -,q++,直到 s p ≠ s q s_p \neq s_q sp=sq。返回长度q-p+1一定为偶数。
    b. len2 = 以 s i s_i si为中心的最长回文子串长度,令双指针起始位置为p = i-1,q = i+1,同样不断令p- -,q++,直到 s p ≠ s q s_p \neq s_q sp=sq。返回长度q-p+1一定为奇数。
  • 将maxLen与len1、len2比较,不断更新maxLen和start。
  • 最后返回子串 s s t a r t , . . . , s s t a r t + m a x L e n − 1 s_{start}, ..., s_{start + maxLen - 1} sstart,...,sstart+maxLen1

需要遍历 2 n − 1 2n-1 2n1个位置,每个位置最多需要时长 min ⁡ { i , n − i } \min\{i, n-i\} min{i,ni},总时长大约为 n ( n − 2 ) 2 \frac{n(n-2)}{2} 2n(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)

运行结果:
在这里插入图片描述

Solution (Java)

DP:

class Solution {
    public String longestPalindrome(String s) {
        int N = s.length();
        if(N < 2) return s;
        int[] len = new int[N];
        int maxLen = 1;
        int end = 0;
        len[0] = 1;
        for(int i = 1; i < N; i++){
            int symmetry = i-1-len[i-1];
            if(symmetry < 0 || s.charAt(symmetry) != s.charAt(i)){
                len[i] = findSym(s, symmetry+1, i);
            }
            else{
                len[i] = len[i-1] + 2;
            }
            if(len[i] > maxLen){
                maxLen = len[i];
                end = i;
            }
        }
        return s.substring(end - maxLen + 1, end + 1);
    }
    private int findSym(String s, int start, int end){
        for(int i = start; i < end; i++){
            if(s.charAt(i) == s.charAt(end)){
                int p = i+1;
                int q = end-1;
                while(p <= q && s.charAt(p) == s.charAt(q)){
                    p++;
                    q--;
                }
                if(p > q) return end - i + 1;
            }
        }
        return 1;
    }
}

中心扩散:

class Solution {
    public String longestPalindrome(String s) {
        int N = s.length();
        if(N < 2) return s;
        int maxLen = 1;
        int start = 0;
        int len1, len2;
        for(int i = 1; i < N; i++){
            len1 = calcLen(s, i - 1, i); // center: (i - 1/2)
            len2 = calcLen(s, i - 1, i + 1); // center: i
            if(len1 > maxLen){
                maxLen = len1;
                start = i - len1/2;
            }
            if(len2 > maxLen){
                maxLen = len2;
                start = i - len2/2;
            }
        }
        return s.substring(start, start + maxLen);
    }
    private int calcLen(String s, int start1, int start2){
        int N = s.length();
        while(start1 >= 0 && start2 < N && s.charAt(start1) == s.charAt(start2)){
            start1--;
            start2++;
        }
        return start2 - start1 - 1;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值