LeetCode0005

  • 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
  • 示例 1:
  • 输入: "babad"
  • 输出: "bab"
  • 注意: "aba" 也是一个有效答案,但我们只输出第一个满足条件的字符串。

思路

  • 中心扩展法:这段说明来自LeetCode官网,事实上,只需使用恒定的空间,我们就可以在 O(n^2)的时间内解决这个问题。我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有2n−1个这样的中心。你可能会问,为什么会是2n - 1个,而不是n个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如“abba” 的中心在两个‘b’ 之间)。
  • 按照官方的写法,我们需要以某个字符为中心,假定它是奇数回文串的中心,扩展一次,然后假定它是偶数回文串的中心,扩展一次。比较奇数串和偶数串两个回文串长度,最后确认回文串最大长度。这里我们提前借用一下马拉车算法的想法,改进一下官方给的中心扩展法。我们对输入的字符串,每两个中间插入一个特殊字符,譬如"#",将形如"abcba"的奇数串变成"#a#b#c#b#a#",将偶数串"abba"变成"#a#b#b#a#",这样他们就都是奇数串了,我们只按照奇数串的中心进行扩展来算即可,得到最大子串后,长度除以2,将子串替换掉特殊字符就得到最大子串了。
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
if (s.length === 0) return '';
let sp = '#';
let str = sp + s.split('').join(sp) + sp;
let maxRadius = -1;
let maxCenter = -1;
for (let i = 0, lens = str.length; i < lens; i++) {
let radius = expandAroundCenter(str, i);
if (radius > maxRadius) {
maxRadius = radius;
maxCenter = i;
}
} return s.slice(maxCenter / 2 - maxRadius / 2 + 1, maxCenter / 2 + maxRadius / 2);
}; function expandAroundCenter(s, center) {
let radius = 1;
let lens = s.length;
let left, right;
do {
left = center - radius;
right = center + radius;
if (left < 0 || right > lens - 1) break;
if (s[left] === s[right]) {
radius++;
} else {
break;
}
} while (true);
return radius;
}
  • 由于我们将长度为n的字符串扩展成长度为2n+1的字符串了,所以最差的情况下,我们比较的次数并不比官网的写法比较2轮(一次将该字符当成奇数回文串中心,一次当成偶数回文串中心)n个字符共计2n次快多少,我们能不能进一步优化,使时间减少一半,变成n呢?
  • 改进:很容易想到,如果有最大的回文字符串,从最中心开始往外扩展,肯定能先找到最大的串,因此可以从扩展后的字符串中心字符开始往两端查找最大回文串,当找到第i个时,如果剩下的字符串不够最大半径时,也就是不可能比当前已经得到的最大字符串大了,就直接退出。代码如下:
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
if (s.length === 0) return '';
let sp = '#';
let str = sp + s.split('').join(sp) + sp;
let maxRadius = -1;
let maxCenter = -1;
let lens = str.length;
let middle = Math.floor(lens / 2);
for (let i = middle; i < lens; i++) {
if (lens - i < maxRadius) break;
let radius = expandAroundCenter(str, i);
if (radius > maxRadius) {
maxRadius = radius;
maxCenter = i;
}
} for (let i = middle; i > -1; i--) {
if (i < maxRadius - 1) break;
let radius = expandAroundCenter(str, i);
if (radius > maxRadius) {
maxRadius = radius;
maxCenter = i;
}
} return s.slice(maxCenter / 2 - maxRadius / 2 + 1, maxCenter / 2 + maxRadius / 2);
}; function expandAroundCenter(s, center) {
let radius = 1;
let lens = s.length;
let left, right;
do {
left = center - radius;
right = center + radius;
if (left < 0 || right > lens - 1) break;
if (s[left] === s[right]) {
radius++;
} else {
break;
}
} while (true);
return radius;
}
  • 可以看到时间已经减少为普通方式的一半了。

  • Manacher(马拉车)算法:马拉车算法 Manacher's Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性O(n)。

  • 核心思想如下:

  • 先通过插入字符法将长度为n的字符串变成长度2n+1的字符串;

  • 用一个数组Radius[2n+1]记录下以每个字符i为中心的最长半径;

  • 对于已经计算过的最长子串,记录下它的对称中心Center,以及它的最右边界RBorder = Center + Radius[Center];

  • 对于当前判断到的字符i,以它为中心的最大子串可能会有以下情况:

  • 假如这个字符在最右边界里,也就是i<=RBorder,那么它关于对称中心Center一定存在一个对称点i_symmetric,使得Raidus[i] = Radius[i_sysmmetric],又因为Center - i_symmetric = i - Center,也就是i_symmetric = 2*Center - i;

  • 考虑此时以i为中心点的最大子串,其最右边界可能会大于我们现有的RBorder,如下图的i2;也有可能比现有的RBorder小,如下图的i1;对i1来说比较简单,它的Radius[i] = Radius[i_sysmmetric]即可;但对于i2来说,它就需要重新计算,但它至少可以从RBorder - i为半径开始重新计算。

# a # b # c # b # a # b # c # b # a #
Center | | RBorder
i1 i2
  • 假如这个字符的最右边界超过最大子串的最右边界了,也就是i + radius[i]>RBorder了,就重新更新边界和Center,因为不超过原RBorder里的字符都处理完了。
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
//扩展字符串
if (s.length === 0) return '';
let sp = '#';
let str = sp + s.split('').join(sp) + sp; let center = -1;
let radius = [];
let rBorder = -1;
//这俩变量只是为了输出第一个最大子串,因为越往后走,center和rborder就往后走了
let maxCenter = -1, maxRadius = -1; for (let i = 0, lens = str.length; i < lens; i++) {
radius[i] = rBorder > i ? Math.min(radius[2 * center - i], rBorder - i) : 1;
while (str[i + radius[i]] && str[i - radius[i]] && str[i + radius[i]] === str[i - radius[i]]) {
radius[i]++;
} if (i + radius[i] > rBorder) {
center = i;
rBorder = i + radius[i];
} //仅仅只是为了记录第一个最长子串,不是核心算法
if (radius[i] > maxRadius) {
maxCenter = i;
maxRadius = radius[i];
}
} return s.slice(maxCenter / 2 - maxRadius / 2 + 1, maxCenter / 2 + maxRadius / 2);
}

LeetCode Day 5的更多相关文章

  1. 我为什么要写LeetCode的博客?

    # 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...

  2. LeetCode All in One 题目讲解汇总(持续更新中...)

    终于将LeetCode的免费题刷完了,真是漫长的第一遍啊,估计很多题都忘的差不多了,这次开个题目汇总贴,并附上每道题目的解题连接,方便之后查阅吧~ 477 Total Hamming Distance ...

  3. [LeetCode] Longest Substring with At Least K Repeating Characters 至少有K个重复字符的最长子字符串

    Find the length of the longest substring T of a given string (consists of lowercase letters only) su ...

  4. Leetcode 笔记 113 - Path Sum II

    题目链接:Path Sum II | LeetCode OJ Given a binary tree and a sum, find all root-to-leaf paths where each ...

  5. Leetcode 笔记 112 - Path Sum

    题目链接:Path Sum | LeetCode OJ Given a binary tree and a sum, determine if the tree has a root-to-leaf ...

  6. Leetcode 笔记 110 - Balanced Binary Tree

    题目链接:Balanced Binary Tree | LeetCode OJ Given a binary tree, determine if it is height-balanced. For ...

  7. Leetcode 笔记 100 - Same Tree

    题目链接:Same Tree | LeetCode OJ Given two binary trees, write a function to check if they are equal or ...

  8. Leetcode 笔记 99 - Recover Binary Search Tree

    题目链接:Recover Binary Search Tree | LeetCode OJ Two elements of a binary search tree (BST) are swapped ...

  9. Leetcode 笔记 98 - Validate Binary Search Tree

    题目链接:Validate Binary Search Tree | LeetCode OJ Given a binary tree, determine if it is a valid binar ...

  10. Leetcode 笔记 101 - Symmetric Tree

    题目链接:Symmetric Tree | LeetCode OJ Given a binary tree, check whether it is a mirror of itself (ie, s ...

随机推荐

  1. 和我一起从0学算法(C语言版)(二)

    第一章 排序 第三节 快速排序 快速排序是最常用的排序方法.快排运用的递归方法很有意思.掌握了这种排序方法可以在将来学习递归时更快入门.只是快排的思路与之前的排序方法相比较为复杂,再加担心上我的表达能 ...

  2. Dynamics CRM - 在 Dynamics CRM 开发中创建一个 Entity 对象

    在 Dynamics CRM 的开发中,我们时不时需要创建 Entity 对象,而对于如何创建 Entity 对象,在 C# plugin 和 JS 的写法存在些许差异. 一.C# Plugin 创建 ...

  3. fatal error C1189: #error: "You must define TF_LIB_GTL_ALIGNED_CHAR_ARRAY for your compiler."

    使用VS开发tensorflow的C++程序的时候,就可能会遇上这个问题,解决方法是在引入tensoflow的头文件之前添加: #define COMPILER_MSVC #define NOMINM ...

  4. mod_rewrite是Apache的一个非常强大的功能

    mod_rewrite是Apache的一个非常强大的功能,它可以实现伪静态页面.下面我详细说说它的使用方法!对初学者很有用的哦! 1.检测Apache是否支持mod_rewrite 通过php提供的p ...

  5. linux系统终端介绍

    https://zhidao.baidu.com/question/174261014.html

  6. UML-SSD-定义

    1.NextGen例子 SSD来自用例文本 2.定义 1).针对的是用例的一个特定场景 2).参与者与系统之间交互事件(跨系统边界,不画系统内部流转,即黑盒) 比如:收银员 访问系统A.系统B,此时只 ...

  7. apk反编译安装工具

    一.需要工具 apktool:反编译APK文件,得到classes.dex文件,同时也能获取到资源文件以及布局文件. dex2jar:将反编译后的classes.dex文件转化为.jar文件. jd- ...

  8. Codeforces Round #523 (Div. 2) Cdp

    题:https://codeforces.com/contest/1061/problem/C 题意:给你一个序列,我们求他们子序列的个数,这个子序列有个限制就是每一个子序列上的值都必须是能整除他的下 ...

  9. Linux之seq命令

    作用:seq命令用于以指定增量从首数开始打印数字到尾数,即产生从某个数到另外一个数之间的所有整数,并且可以对整数的格式.宽度.分割符号进行控制 语法: [1]  seq [选项]    尾数 [2]  ...

  10. 三十、sersync高级同步工具实时数据同步架构

    一.项目介绍 Sersync项目利用inotity与rsync技术实现对服务器数据实时同步的解决方案,其中inotity用于监控sersync所在服务器上的文件变化. Sersync项目的优点: 1. ...