没有合适的资源?快使用搜索试试~ 我知道了~
目录目录经典算法Manacher算法原始问题进阶问题BFPRT算法morris遍历二叉树遍历规则先序、中序序列后序序列时间复杂度分析求和为aim的最长子数组长度
资源详情
资源评论
资源推荐
目录
目录
经典算法
Manacher算法
原始问题
进阶问题
BFPRT算法
morris遍历二叉树
遍历规则
先序、中序序列
后序序列
时间复杂度分析
求和为aim的最长子数组长度
拓展
求奇数个数和偶数个数相同的最长子数组长度
求数值为1的个数和数值为2的个数相同的最长子数组(数组只含0、1、2三种元素)
进阶
求任意划分数组的方案中,划分后,异或和为0的子数组最多有多少个
高度套路的二叉树信息收集问题
求一棵二叉树的最大搜索二叉子树的结点个数
求一棵二叉树的最远距离
舞会最大活跃度
求一个数学表达式的值
求异或和最大的子数组
暴力解
优化暴力解
最优解
求和为aim的最长子数组(都大于0)
求和小于等于aim的最长子数组(有正有负有0)
环形单链表的约瑟夫问题
经典结构
窗口最大值更新结构
最大值更新结构
例题
窗口移动
求达标的子数组个数
单调栈结构
原始问题
给你一些数,创建一棵大根堆二叉树
找出矩阵中一片1相连的最大矩形
烽火相望
1、数组中无重复的数
2、数组中可能有重复的数
搜索二叉树
平衡二叉树/AVL树
平衡性
典型搜索二叉树——AVL树、红黑树、SBT树的原理
AVL树
红黑树
SBT树
旋转——Rebalance
Java中红黑树的使用
案例
The Skyline Problem
跳表
添加数据
查找数据
删除数据
遍历数据
从暴力尝试到动态规划
换钱的方法数
暴力尝试
缓存每个状态的结果,以免重复计算
确定依赖关系,寻找最优解
排成一条线的纸牌博弈问题
暴力尝试
改动态规划
机器人走路问题
字符串正则匹配问题
暴力尝试
动态规划
缓存结构的设计
设计可以变更的缓存结构(LRU)
LFU
附录
手写二叉搜索树
AbstractBinarySearchTree
AbstractSelfBalancingBinarySearchTree
AVLTree
RedBlackTree
经典算法
Manacher算法
原始问题
Manacher算法是由题目“求字符串中最长回文子串的长度”而来。比如 abcdcb 的最长回文子串为 bcdcb ,其长度
为5。
我们可以遍历字符串中的每个字符,当遍历到某个字符时就比较一下其左边相邻的字符和其右边相邻的字符是否相
同,如果相同则继续比较其右边的右边和其左边的左边是否相同,如果相同则继续比较……,我们暂且称这个过程
为向外“扩”。当“扩”不动时,经过的所有字符组成的子串就是以当前遍历字符为中心的最长回文子串。
我们每次遍历都能得到一个最长回文子串的长度,使用一个全局变量保存最大的那个,遍历完后就能得到此题的
解。但分析这种方法的时间复杂度:当来到第一个字符时,只能扩其本身即1个;来到第二个字符时,最多扩两
个;……;来到字符串中间那个字符时,最多扩 (n-1)/2+1 个;因此时间复杂度为 1+2+……+(n-1)/2+1 即
O(N^2) 。但Manacher算法却能做到 O(N) 。
Manacher算法中定义了如下几个概念:
回文半径:串中某个字符最多能向外扩的字符个数称为该字符的回文半径。比如 abcdcb 中字符 d ,能扩一
个 c ,还能再扩一个 b ,再扩就到字符串右边界了,再算上字符本身,字符 d 的回文半径是3。
回文半径数组 pArr :长度和字符串长度一样,保存串中每个字符的回文半径。比如 charArr="abcdcb" ,
其中 charArr[0]='a' 一个都扩不了,但算上其本身有 pArr[0]=1 ;而 charArr[3]='d' 最多扩2个,算上
其本身有 pArr[3]=3 。
最右回文右边界 R :遍历过程中,“扩”这一操作扩到的最右的字符的下标。比如 charArr=“abcdcb” ,当遍
历到 a 时,只能扩 a 本身,向外扩不动,所以 R=0 ;当遍历到 b 时,也只能扩 b 本身,所以更新 R=1 ;
但当遍历到 d 时,能向外扩两个字符到 charArr[5]=b ,所以 R 更新为5。
最右回文右边界对应的回文中心 C : C 与 R 是对应的、同时更新的。比如 abcdcb 遍历到 d 时, R=5 ,
C 就是 charArr[3]='d' 的下标 3 。
处理回文子串长度为偶数的问题:上面拿 abcdcb 来举例,其中 bcdcb 属于一个回文子串,但如果回文子串长度
为偶数呢?像 cabbac ,按照上面定义的“扩”的逻辑岂不是每个字符的回文半径都是0,但事实上 cabbac 的最长
回文子串的长度是6。因为我们上面“扩”的逻辑默认是将回文子串当做奇数长度的串来看的,因此我们在使用
Manacher算法之前还需要将字符串处理一下,这里有一个小技巧,那就是将字符串的首尾和每个字符之间加上一
个特殊符号,这样就能将输入的串统一转为奇数长度的串了。比如 abba 处理过后为 #a#b#b#a ,这样的话就有
charArr[4]='#' 的回文半径为4,也即原串的最大回文子串长度为4。相应代码如下:
接下来分析,BFPRT算法是如何利用遍历过程中计算的 pArr 、 R 、 C 来为后续字符的回文半径的求解加速的。
首先,情况1是,遍历到的字符下标 cur 在 R 的右边(起初另 R=-1 ),这种情况下该字符的最大回文半径
pArr[cur] 的求解无法加速,只能一步步向外扩来求解。
情况2是,遍历到的字符下标 cur 在 R 的左边,这时 pArr[cur] 的求解过程可以利用之前遍历的字符回文半径信
息来加速。分别做 cur 、 R 关于 C 的对称点 cur' 和 L :
如果从 cur' 向外扩的最大范围的左边界没有超过 L ,那么 pArr[cur]=pArr[cur'] 。
public static char[] manacherString(String str){
char[] source = str.toCharArray();
char chs[] = new char[str.length() * 2 + 1];
for (int i = 0; i < chs.length; i++) {
chs[i] = i % 2 == 0 ? '#' : source[i / 2];
}
return chs;
}
1
2
3
4
5
6
7
8
证明如下:
由于之前遍历过 cur' 位置上的字符,所以该位置上能扩的步数我们是有记录的( pArr[cur'] ),也就是
说 cur'+pArr[cur'] 处的字符 y' 是不等于 cur'-pArr[cur'] 处的字符 x' 的。根据 R 和 C 的定义,整
个 L 到 R 范围的字符是关于 C 对称的,也就是说 cur 能扩出的最大回文子串和 cur' 能扩出的最大回文子
串相同,因此可以直接得出 pArr[cur]=pArr[cur'] 。
如果从 cur' 向外扩的最大范围的左边界超过了 L ,那么 pArr[cur]=R-cur+1 。
证明如下:
R 右边一个字符 x , x 关于 cur 对称的字符 y , x,y 关于 C 对称的字符 x',y' 。根据 C,R 的定义有
x!=x' ;由于 x',y' 在以 cur' 为中心的回文子串内且关于 cur' 对称,所以有 x'=y' ,可推出 x!=y' ;
又 y,y' 关于 C 对称,且在 L,R 内,所以有 y=y' 。综上所述,有 x!=y ,因此 cur 的回文半径为 R-
cur+1 。
以 cur' 为中心向外扩的最大范围的左边界正好是 L ,那么 pArr[cur] >= (R-cur+1)
这种情况下, cur' 能扩的范围是 cur'-L ,因此对应有 cur 能扩的范围是 R-cur 。但 cur 能否扩的更大则
取决于 x 和 y 是否相等。而我们所能得到的前提条件只有 x!=x' 、 y=y' 、 x'!=y' ,无法推导出 x,y 的
关系,只知道 cur 的回文半径最小为 R-cur+1 (算上其本身),需要继续尝试向外扩以求解 pArr[cur] 。
综上所述, pArr[cur] 的计算有四种情况:暴力扩、等于 pArr[cur'] 、等于 R-cur+1 、从 R-cur+1 继续向外
扩。使用此算法求解原始问题的过程就是遍历串中的每个字符,每个字符都尝试向外扩到最大并更新 R (只增不
减),每次 R 增加的量就是此次能扩的字符个数,而 R 到达串尾时问题的解就能确定了,因此时间复杂度就是每
次扩操作检查的次数总和,也就是 R 的变化范围( -1~2N ,因为处理串时向串中添加了 N+1 个 # 字符),即
O(1+2N)=O(N) 。
整体代码如下:
public static int maxPalindromeLength(String str) {
char charArr[] = manacherString(str);
int pArr[] = new int[charArr.length];
int R = -1, C = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i < charArr.length; i++) {
pArr[i] = i > R ? 1 : Math.min(pArr[C * 2 - i], R - i);
while (i + pArr[i] < charArr.length && i - pArr[i] > -1) {
if (charArr[i + pArr[i]] == charArr[i - pArr[i]]) {
pArr[i]++;
} else {
break;
}
}
if (R < i + pArr[i]) {
R = i + pArr[i]-1;
C = i;
}
max = Math.max(max, pArr[i]);
}
return max-1;
}
public static void main(String[] args) {
System.out.println(maxPalindromeLength("zxabcdcbayq"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
剩余112页未读,继续阅读
老许的花开
- 粉丝: 20
- 资源: 328
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0