全国旗舰校区

不同学习城市 同样授课品质

北京

深圳

上海

广州

郑州

大连

武汉

成都

西安

杭州

青岛

重庆

长沙

哈尔滨

南京

太原

沈阳

合肥

贵阳

济南

下一个校区
就在你家门口
+
当前位置:首页  >  大数据学习笔记  >  详情

大数据之最长回文串

来源:千锋教育
发布人:qyf
2022-12-07

推荐

在线提问>>

大数据之最长回文串

  题目描述

  给定一个字符串,要求这个字符串当中最长的回文串。

  示例

  Input: "babad"

  Output: "bab"

  Note: "aba" is also a valid answer.

  Input: "cbbd"

  Output: "bb"

  题目分析

  这道题目是典型的看着简单,但是实际上并不简单的问题。

  我们先从简单的算法开始,最简单的方法当然是暴力。由于我们需要求出最长的回文串,一种方法是求出s串所有的子串,然后一一对比它们是否构成回文。这样当然是可行的,但是我们简单分析一下复杂度就会发现,这并不能接受。对于一个长度为n的字符串来说,我们任意选择其中两个位置,就可以找到它的一个子串,那么我们选择两个位置的数量就是$C_n^2 = \frac{n(n-1)}{2}$。对于每一个子串,我们需要遍历一遍才能判断是否回文,所以整体的复杂度是$O(n^3)$。

  但是如果你对回文串非常熟悉的话,会发现其实这是可以优化的。因为我们要求的是最长的回文串,如果我们确定了对称中心的位置,它能够构成的最长回文串就是确定的。所以我们只需要遍历所有的回文串中心,和每个中心能找到的最长回文串。这样我们的复杂度就降低了一维,变成了$O(n^2)$。

  回文串有两种形式,一种是奇回文,也就是回文中心是一个字符,比如aba。还有一种是偶回文,回文中心是两个字符之间,比如abba。这两种情况我们需要分开讨论。

  我们写出代码:

  class Solution:

  def longestPalindrome(self, s: str) -> str:

  n = len(s)

  ret = ''

  for i in range(n):

  # 奇回文的情况

  l, r = i, i

  while s[l] == s[r]:

  l -= 1

  r += 1

  if l < 0 or r >= n:

  break

  if r - l - 1 > len(ret):

  ret = s[l+1: r]

  # 偶回文的情况

  l, r = i-1. i

  while l >= 0 and s[l] == s[r]:

  l -= 1

  r += 1

  if l < 0 or r >= n:

  break

  if r - l - 1 > len(ret):

  ret = s[l+1: r]

  return ret

  到这里还没有结束,接下来我们介绍一个经典的回文串求解算法——Manacher,也叫做马拉车算法。

  首先,我们需要统一奇回文和偶回文这两种情况,这也很方便,我们把原串进行处理,在两个相邻字符当中插入一个分隔字符#,比如abcd转化成#a#b#c#d#。一般我们还会在首尾加入防止超界的字符,比如$&等。之后我们维护两个值,分别是id和mr。mr表示当前能够构成的回文串向右延伸最远的位置,id表示这个位置对应的对称中心。根据这个位置id以及mr我们可以快速地求解出当前位置i能够构成的合法回文串的长度。

  我们假设每一个位置构成的合法回文串半径是p[i], 那么对于i这个位置,我们可以得到p[i] >= min(mr - i, p[id * 2 - i])。其中id * 2 - i是i这个位置关于id的对称位置,并且以i为中心对称的回文串小于mr位置的部分也关于id对称。所以如果p[id * 2 - i] < mr - i的话,说明i关于id的对称位置没能突破id对称的限制,既然i的对称点没有能突破限制,那么i显然也不行。同理,如果p[id * 2 - i] > mr - i的话,说明i的对称位置没有被id限制住,但是这恰恰说明i被限制住了。因为如果i也能突破mr这个限制的话,那么说明id的对称范围还能扩大,这和我们的前提假设矛盾了。所以只有p[id * 2 - i] == mr - i的情况,i才有可能继续延伸。

  如果能理解上面的关系,整个算法已经很清楚了,如果没看懂也没有关系,可以看下下面的动图,会展示得更加清楚。

  理解了上述的算法过程之后剩下的工作就简单了,我们只需要在求解p[i]的同时维护id和mr即可。

  最后我们来看下算法的复杂度,为什么这是一个O(n)的算法呢?原因很简单,我们只需要关注mr这个变量即可。mr这个变量是递增的,mr每次递增的大小,其实就是p[i] - (mr - i)的长度。所以虽然看似我们用了两重循环,但是由于mr最多只能递增n次,所以它依然是O(n)的算法。

  代码实现

  class Solution:

  def longestPalindrome(self, s: str) -> str:

  # 在所有字符中间插入#

  def transform(s):

  return '$#' + '#'.join(list(s)) + '#&'

  if s == '':

  return s

  # 初始化

  s = transform(s)

  p = [0 for _ in range(len(s)+1)]

  mr, id_ = 0. 0

  # 首尾是特殊字符,所以下标从1到len(s)-2

  for i in range(1. len(s)-1):

  # 计算p[i]

  p[i] = 1 if mr <= i else min(p[2*id_-i], mr - i)

  # 只有当前i已经摆脱id限制,或者是第三种情况时,才有可能继续延伸

  # 这个只是优化,不加这个判断一样可以运行

  if mr <= i or p[2*id_-i] == mr - i:

  while s[i - p[i]] == s[i + p[i]]:

  p[i] += 1

  if i + p[i] > mr:

  mr, id_ = i + p[i], i

  # 找到长度最长的下标

  id_ = p.index(max(p))

  # 获得整个回文的字符串

  palindromic = s[id_ - p[id_]+1: id_ + p[id_]]

  # 过滤掉#,还原为原字符

  return ''.join(filter(lambda x: x != '#', list(palindromic)))

相关文章

大数据之什么是数仓

2022-12-08

手写算法-懒汉式单例

2022-12-08

手写算法-四大排序

2022-12-08

是一个宽表好还是多个维表好?

2022-12-08

数据库和数据仓库的区别是什么?

2022-12-08

“未知”的数据对数据分析和可视化有什么影响?好处和坏处是什么?

2022-12-08
在线咨询 免费试学 教程领取