JavaScript求解最长回文子串的方法分享

 更新时间:2022年08月05日 15:49:53   作者:Ethan_Zhou  
这篇文章主要为大家介绍了JavaScript求解最长回文子串的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

题目描述

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

示例 1:

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

示例 2:

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

题解

回文:指一个正读和反读都相同的字符串,例如,“aba” 是回文,而 “abc” 不是。

解决方案

思路一:暴力法

即通过双重遍历来获取目标字符串所有的子串,push 到一个数组里面,然后根据字符串长度排序,从最长字串开始循环校验,第一个为回文的子串就是我们要的结果

复杂度分析

  • 时间复杂度:O(n^3)
  • 空间复杂度:O(1)
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let m = []
    let res = ''
    for(let i=0; i<s.length; i++) {
        for(let j = i; j < s.length; j++) {
            m.push(s.slice(i, j+1))
        }
    }
    m.sort(function(a,b){
        return b.length - a.length
    })
    for (let i of m) {
        if (i === i.split('').reverse().join('')) {
            res = i
            break;
        }
    }
    return res
}

上面代码在目标字符串长度过大的时候,会超出时间限制,远远超出O(n^2) 的理想时间复杂度,这是因为过多的for 循环,js 自带函数使用过多造成的,优化一下

var longestPalindrome = function(s) {
    let m = []
    let res = ''
    for(let i=0; i<s.length; i++) {
        for(let j = i; j < s.length; j++) {
            if (s[i] != s[j]) break
            let ele = s.slice(i, j+1)
            if (ele === ele.split('').reverse().join('') && ele.length > res.length) {
                res = ele
            }
        }
    }
    return res
}

看起来好多了,但是依然通不过Leecode 的测试,我觉得必须要把 slice split reverse join 这些函数都干掉才行,也可能 JS 语言效率确实慢一些

思路二:最长公共字串

反转 S,使之变成 S'。找到 S 和 S' 之间最长的公共子串,判断是否是回文

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n^2)
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let rs = s.split('').reverse().join('')
    let size = s.length
    let len = 0
    let end = 0
    let a = new Array(size)
    for (let i = 0; i < size; i++) {
        a[i] = new Array()
    }
    for (let i = 0; i < size; i++) {
        for(let j = 0; j < size; j++) {
            if (s[i] === rs[j]) {
                if (i > 0 && j > 0) {
                    a[i][j] = a[i-1][j-1] + 1
                } else {
                    a[i][j] = 1
                }

                if(a[i][j] > len) {
                    let beforeIndex = size - 1 - j
                    if (beforeIndex + a[i][j] -1 == i) { 
                        len = a[i][j]
                        end = i
                    }
                }
            } else {
                a[i][j] = 0
            }
        }
    }
    return s.slice(end-len+1, end+1)
}

思路三:中心拓展

遍历一遍字符串,以每个字符为中心向两边判断,是否为回文字符串

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let size = s.length
    let start = 0
    let len = 0 //字串长度
    // 奇数长度的回文字串
    for (let i = 0; i < size; i++) {
        let left = i - 1
        let right = i + 1
        while (left >= 0 && right < size && s[left] == s[right]) {
            left --
            right ++
        }
        if (right - left - 1 > len) {
            start = left +1
            len = right -left -1
        }
    }
    // 偶数长度的回文字串
    for (let i = 0; i < size; i++) {
        let left = i
        let right = i + 1
        while (left >= 0 && right < size && s[left] == s[right]) {
            left--
            right++
        }
        if (right -left -1 > len) {
            start = left + 1
            len = right -left -1
        }
    }
    return s.slice(start, start + len)
}

思路四:Manacher 算法

manacher 算法涉及中心拓展法、动态规划,是manacher 1975年发明出来用来解决最长回文子串的线性算法

// 第一步
var longestPalindrome = function(s) {
    let size = s.length
    if (size < 2) {
        return s
    }
    let str = addBoundaries(s, '#')
    let formatSize = 2 * size +1
    let maxSize = 1

    let start = 0
    for (let i=0; i<formatSize; i++) {
        let curSize = centerSpread(str, i)
        if (curSize > maxSize) {
            maxSize = curSize
            start = (i - maxSize)/2
        }
    }
    return s.slice(start, start + maxSize)
}

// 处理原字符串
var addBoundaries = function(s, divide) {
    let size = s.length
    if (size === 0) {
        return ''
    }
    if (s.indexOf(divide) != -1) {
        throw new Error('参数错误,您传递的分割字符,在输入字符串中存在!')
    }
    return divide + s.split('').join(divide) + divide
}

// 辅助数组
var centerSpread = function(s, center) {
     // left = right 的时候,此时回文中心是一个空隙,回文串的长度是奇数
    // right = left + 1 的时候,此时回文中心是任意一个字符,回文串的长度是偶数
    let len = s.length
    let i = center - 1
    let j = center + 1
    let step = 0
    while (i >= 0 && j < len && s.charAt(i) == s.charAt(j)) {
        i--
        j++
        step++
    }
    return step
}
longestPalindrome('ababadfglldf;hk;lhk')

manacher 算法的工作,就是对上面代码中的辅助数组 p 进行优化,使时间复杂度的降到O(n^2)

// 完整
var longestPalindrome = function(s) {
    let size = s.length
    if (size < 2) {
        return s
    }
    let str = addBoundaries(s, '#')
    let formatSize = 2 * size +1

    let p = new Array(formatSize).fill(0)

    let maxRight = 0
    let center = 0

    let maxSize = 1

    let start = 0
    for (let i=0; i<formatSize; i++) {
        if (i < maxRight) {
            let mirror = 2 * center - i;
            // Manacher 算法的核心
            p[i] = Math.min(maxRight - i, p[mirror]);
        }
        // 下一次尝试扩散的左右起点,能扩散的步数直接加到 p[i] 中
        let left = i - (1 + p[i]);
        let right = i + (1 + p[i]);

        // left >= 0 && right < formatSize 保证不越界
        // str.charAt(left) == str.charAt(right) 表示可以扩散 1 次
        while (left >= 0 && right < formatSize && str.charAt(left) == str.charAt(right)) {
            p[i]++;
            left--;
            right++;

        }
        // 根据 maxRight 的定义,它是遍历过的 i 的 i + p[i] 的最大者
        // 如果 maxRight 的值越大,进入上面 i < maxRight 的判断的可能性就越大,这样就可以重复利用之前判断过的回文信息了
        if (i + p[i] > maxRight) {
            // maxRight 和 center 需要同时更新
            maxRight = i + p[i];
            center = i;
        }
        if (p[i] > maxSize) {
            // 记录最长回文子串的长度和相应它在原始字符串中的起点
            maxSize = p[i];
            start = (i - maxSize) / 2;
        }
    }
    return s.slice(start, start + maxSize)
}

var addBoundaries = function(s, divide) {
    let size = s.length
    if (size === 0) {
        return ''
    }
    if (s.indexOf(divide) != -1) {
        throw new Error('参数错误,您传递的分割字符,在输入字符串中存在!')
    }
    return divide + s.split('').join(divide) + divide
}
longestPalindrome('babb')

到此这篇关于JavaScript求解最长回文子串的方法分享的文章就介绍到这了,更多相关JavaScript最长回文子串内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript算法教程之sku(库存量单位)详解

    JavaScript算法教程之sku(库存量单位)详解

    这篇文章主要给大家介绍了JavaScript算法教程之sku(库存量单位)的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编一起来学习学习吧。
    2017-06-06
  • javascript实现英文首字母大写

    javascript实现英文首字母大写

    本文给大家总结了几种可以实现英文首字母大写的javascript脚本,另附上一个CSS的实现方法,非常的简单实用,这里推荐给大家,有需要的小伙伴可以参考下。
    2015-04-04
  • JS实现链表数据结构的代码详解

    JS实现链表数据结构的代码详解

    很多前端的同学对数据结构和算法这块没有太多的概念,很多leetcode的题目看不懂,有时候可能看了题解也不知道是什么意思,这篇文章咱们来简单的谈一谈链表,文中给大家介绍了JS实现链表数据结构的示例代码,需要的朋友可以参考下
    2024-01-01
  • iframe的父子窗口之间的对象相互调用基本用法

    iframe的父子窗口之间的对象相互调用基本用法

    iframe在使用时可能会涉及到父子窗口之间传值和方法的相互调用,研究了一下其实非常简单,就那么几个用法而已,在此与大家分享下,感兴趣的朋友可以参考下
    2013-09-09
  • 小程序图片剪裁加旋转的示例代码

    小程序图片剪裁加旋转的示例代码

    这篇文章主要介绍了小程序图片剪裁的示例代码,可以通过手势控制旋转缩放移动,也可以点击旋转进行90度旋转,非常具有实用价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • js中浮点型运算BUG的解决方法说明

    js中浮点型运算BUG的解决方法说明

    本篇文章主要是对js中浮点型运算BUG的解决方法进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • Javascript中的delete介绍

    Javascript中的delete介绍

    关于JavaScript中的Delete一直没有弄的很清楚,最近看到两篇这方面的文章,现对两文中部分内容进行翻译(内容有修改和添加,顺序不完全一致,有兴趣推荐看原文),希望能对大家有所帮助
    2012-09-09
  • JavaScript使用yield模拟多线程的方法

    JavaScript使用yield模拟多线程的方法

    这篇文章主要介绍了JavaScript使用yield模拟多线程的方法,实例分析了javascript多线程的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • javascript中style.left和offsetLeft的用法说明

    javascript中style.left和offsetLeft的用法说明

    本篇文章主要是对javascript中style.left和offsetLeft的用法进行了说明介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-03-03
  • javascript性能优化之事件委托实例详解

    javascript性能优化之事件委托实例详解

    这篇文章主要介绍了javascript性能优化之事件委托用法,结合实例形式对比分析了JavaScript中事件委托的具体用法与优点,需要的朋友可以参考下
    2015-12-12

最新评论