中心扩展法:最长回文子串

434 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

题目描述

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

示例 1:

输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2:

输入:s = "cbbd" 输出:"bb" 示例 3:

输入:s = "a" 输出:"a" 示例 4:

输入:s = "ac" 输出:"a"

提示:

1 <= s.length <= 1000 s 仅由数字和英文字母(大写和/或小写)组成

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/lo… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

很显然暴力遍历是一种方法 比如说:

L = (input())

count = 0
len1 = len(L)

for i in range(len1):
    for each in range(i, len1):
        if L[i:each] == L[each:i:-1]:
            len2 = len(L[i:each])
            if len2 > count:
                s = L[i:each+1]
                count = len2

print(s)

但这样一般情况下会超时 所以我们换另一种比较好理解的思路,由子串的中心向两边展开,也就是模拟双指针。 首先我们要找最长回文子串的特点: 我们会发现回文子串都可以分成这两种 1. 长度为偶数就是两两相等,如:abba 2. 长度为奇数就是中心除外两两相等, 如:abcba 3. 无论是aaaa或者aaaaa也可以归到这两类中 知道这几点后就可以实现代码了

代码实现

s = input()

def longestPalindrome(s):

    # 因为下面的代码对 len(s) <= 2时非回文子串无法判定(如:a 或 ac),所以先让 result 等于第一个字符, 这样回文子串的长度为 1
    result = s[0]
    len_res = 1
    
    # 第一种情况: aba 或 aaa
    for i in range(1, len(s)):
        start = i - 1
        end = i + 1
        while True:
            if start > 0 and end < len(s) - 1 and s[start] == s[end]:
                start -= 1
                end += 1
            else:
                break
                
        if start >= 0 and end <= len(s) - 1:
            if s[start] == s[end]:
                if end - start + 1 > len_res:
                    len_res = end - start + 1
                    result = s[start: end+1]
            else:
                # 这里 start += 1 和 end -= 1 是因为满足 start > 0 and end < len(s) - 1 and s[start] == s[end] 时 start -= 1, end += 1。然后我们需要判定边界是否相等, 即:s[start] == s[end],如果不相等,那就退一步,即: start += 1 和 end -= 1
                start += 1
                end -= 1
                # 判断遍历的回文子串是不是最长的
                if end - start + 1 > len_res:
                    len_res = end - start + 1
                    result = s[start: end+1]

    # 第二种情况:abba 或 aaaa
    for i in range(0, len(s)):
        start = i
        end = i+1
        while True:
            if start > 0 and end < len(s) - 1 and s[start] == s[end]:
                start -= 1
                end += 1
            else:
                break
        if start >= 0 and end <= len(s) - 1:
            if s[start] == s[end]:
                if end - start + 1 > len_res:
                    len_res = end - start + 1
                    result = s[start: end+1]
            else:
                start += 1
                end -= 1
                if end - start + 1 > len_res:
                    len_res = end - start + 1
                    result = s[start: end+1]

    return result

print(longestPalindrome(s))

代码优化

我们会发现两种不同的类型的回文子串的查找方式很像,所以我们可以把这些代码模块化,即自定义函数 (这好像是力扣的官方解法)

L = input()

def expendAroundCenter(s, left, right):
    while left >= 0 and right < len(s) and s[left] == s[right]:
        left -= 1
        right += 1
    return left + 1, right - 1

'''
因为满足 left >= 0 and right < len(s) and s[left] == s[right] 后 left -= 1, right += 1
但又因为退出循环 所以 满足回文的是 left + 1 和 right - 1
'''
def longestPalindrome(s):
    start, end = 0, 0
    for i in range(len(s)):
        '''
        若回文子串为奇数,则中间的字只出现一次,如“aba”。
        所以从中间查找,只有 s[i]==s[i]
        '''
        left1, right1 = expendAroundCenter(s, i, i)
        if right1 - left1 > end - start:
            start, end = left1, right1

        '''
        若回文子串为偶数,则字出现两次,如“abba”。
        所以从中间查找,有 s[i]==s[i+1]
        '''
        left2, right2 = expendAroundCenter(s, i, i + 1)
        if right2 - left2 > end - start:
            start, end = left2, right2

    return s[start : end + 1]

print(longestPalindrome(L))