一天一大 lee(回文子串)难度:中等-Day20200819
时间:2022-07-25
本文章向大家介绍一天一大 lee(回文子串)难度:中等-Day20200819,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例
- 示例 1
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
- 示例 2
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
- 输入的字符串长度不会超过 1000 。
抛砖引玉
抛砖引玉
思路
之前做过验证回文串的题目:20200619:验证回文串 (难度:简单)[2]既然可以验证一个字符串是否为回文字符串了,那么就只剩枚举字符串的子区间了
/**
* @param {string} s
* @return {number}
*/
var countSubstrings = function (s) {
let _result = 0
// 枚举区间
for (let i = 0; i < s.length; i++) {
for (let j = i; j < s.length; j++) {
if (check(i, j + 1)) {
_result++
}
}
}
return _result
// 验证回文串
function check(i, j) {
let str = s
.substring(i, j)
.replace(/[^0-9a-zA-Z]/g, '')
.toLowerCase(),
n = str.length,
left = 0,
right = n - 1
while (left < right) {
if (str[left] != str[right]) {
return false
}
left++
right--
}
return true
}
}
中心拓展
通过区间来枚举可能是回文字符串中心位置的元素
- 任何一个可能作为中心位置的元素,可能是唯一的中心也可能是与另外一个元素对称
- 回文字符长度为奇数(唯一的中心)
- 回文字符长度为偶数(与另外一个元素对称)
- 声明两个指针 left,right,来枚举中心位置 中心位置的范围是:0-len-1,指针的范围是:0-2*len-1
var countSubstrings = function (s) {
let len = s.length,
_result = 0
for (let i = 0; i < 2 * len - 1; ++i) {
let left = parseInt(i / 2, 10),
right = Math.ceil(i / 2)
while (left >= 0 && right < len && s.charAt(left) === s.charAt(right)) {
--left
++right
++_result
}
}
return _result
}
动态规划
- 声明两个指针 i,j
- dp[i][j]表示,区间 i->j 的回文字符数量
- 字符串是回文字符的情况:
- 长度为:1
- 长度为: 2,且两个字符相等
- 长度大于 2,首位字符相等,且去掉首位字符的子字符串也是回文字符
var countSubstrings = function (s) {
let len = s.length,
_result = 0,
dp = Array(len)
for (let i = 0; i < len; i++) {
dp[i] = Array(len).fill(false)
}
for (let j = 0; j < len; j++) {
for (let i = 0; i <= j; i++) {
if (i == j) {
dp[i][j] = true
_result++
} else if (j - i == 1 && s[i] == s[j]) {
dp[i][j] = true
_result++
} else if (j - i > 1 && s[i] == s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true
_result++
}
}
}
return _result
}
降维
var countSubstrings = function (s) {
let len = s.length,
_result = 0,
dp = Array(len)
for (let j = 0; j < len; j++) {
for (let i = 0; i <= j; i++) {
if (s[i] == s[j] && (j - i <= 1 || dp[i + 1])) {
dp[i] = true
_result++
} else {
dp[i] = false
}
}
}
return _result
}
Manacher 算法
- 中心拓展:枚举中心位置
- 动态规划:记录子区间是否是回文字符
- 那么可以结合以上两种方法,枚举中心,记录枚举的中心最长的对称字符, 每个枚举的中心位值不在重新计算,可以再上一个中心基础上计算,对称字符的长度用半径表示:
- 枚举一个中心位置,将其看做起点,向外扩展,同时记录扩展的对称半径(radius)+1,扩展的右边界(right)
- f[i]记录 i 点的最大对称半径
初始化对称半径,当前元素索引 i:
- 如果 i <= right,则说明当前枚举的元素在上一个回文串中:
- 那么 i 其中对称点至少是上一个回文字符的子字符,设 j,上一个回文串中与 i 对称的点的索引:j = 2*radius-i
- i 初始对称半径的边界:right-i+1(当 i 与 j 仅间隔一个#时,f(j)可能大于 right-i+1)
- Math.min(f(j),right-i+1);
- 如果 i>right,之前字符暂无与其对称,设置默认半径 1
扩展中心位置:中心位置 i,初始化的对称为 f[i],那么已知:s[i+f(i)-1] === s[i-f(i)+1] 扩张该中心位置的对称字符时,下一个要比较的元素:s[i+f(i)] === s[i-f(i)],如果相同修改 f(i)、即记录的半径变大,知道遇到不相同的元素
为了避免枚举的中心位置可能自己独立成中心也可能与其他元素相同,则修改原字符串 s:在原字符串中间隔插入字符#,并且标记开始结束(开始结束标记不同字符)
返回结果
- s 间隔插入了#,且收尾拼接字符后,那么再计算 f(i)是就存在的无效的 i
- 则累计 f(i)是需要跳过#的无效索引
var countSubstrings = function (s) {
let t = ['$', '#'],
radius = 0,
right = 0,
_result = 0
// 在字符中间隔插入#,首尾添加 $,!
for (let i = 0; i < s.length; ++i) {
t.push(s.charAt(i))
t.push('#')
}
let n = t.length,
f = new Array(n)
t = t.join('')
t += '!'
for (let i = 1; i < n; ++i) {
// 初始化i开始半径
let j = 2 * radius - i
f[i] = i <= right ? Math.min(right - i + 1, f[j]) : 1
// 查找i作为中心位置的最大半径
while (t.charAt(i + f[i]) == t.charAt(i - f[i])) {
++f[i]
}
// i作为中心如果大于上一个有边界则拓展右边界
if (i + f[i] - 1 > right) {
radius = i
right = i + f[i] - 1
}
// 前两位为 $#,均忽略
_result += Math.floor(f[i] / 2)
}
return _result
}
参考资料
[1]
题目:: https://leetcode-cn.com/problems/palindromic-substrings/
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 诺禾致源linux下数据下载
- 技巧 | OpenCV中如何绘制与填充多边形
- Swift guard
- PyTorch实现TPU版本CNN模型
- 使用NLP检测和对抗AI假新闻
- kallisto --genomebam报错解决(GTF文件的坑)
- linux查找文件
- TCP 协议面试灵魂 12 问,问到你怀疑人生!
- 方差分析简介(结合COVID-19案例)
- mysql计算两个时间字段的时间差
- 学生党学编程,有这个开源项目就够了!
- 【最强ResNet改进系列】Res2Net:一种新的多尺度网络结构,性能提升显著
- Java中的锁以及sychronized实现机制(十)
- Web 指纹识别之路
- RedisTemplate常用集合使用说明-opsForHash(四)