Manacher算法
解决的问题:
字符串str中,最长回文子串的长度如何求解
如何做到时间复杂度O(N)完成
伪代码
时间复杂度分析
代码实现
public class Manacher {
public static char[] manacherString(String s) {
char[] charArr = s.toCharArray();
char[] res = new char[s.length() * 2 + 1];
int index = 0;
for (int i = 0; i < res.length; ++i) {
res[i] = (i & 1) == 0 ? '#' : s.charAt(index++);
}
return res;
}
public static int maxLengthOfPalindrome(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] str = manacherString(s);
//回文半径数组
int[] pArr = new int[ str.length];
// 整体最右回文右边界的下一个位置,最右的有效区是R-1位置,与讲解时不一样 L[...C...]R
int R = -1;
// 整体最右回文时的回文半径中心
int C = -1;
//扩出来的最大值
int max = Integer.MIN_VALUE;
//每个位置都求回文半径
for (int i = 0; i < str.length; ++i) {
//不用验证也知道的i至少的回文区域,先给pArr[i],四种情况都对,可以验证
//R <= i i在R外,至少不用验证的区域为1,对应情况1
//R > i i在R内,至少不用验证的区域为为 i‘的位置对应的回文半径 和 R-i中 的最小值,对应 情况2 的1,2,3
//i'的位置 = R - (R - C) * 2 + (R - i) = R - 2R + 2C + R - i = 2 * C - i
pArr[i] = R > i ? Math.min(pArr[ 2 * C - i], R - i) : 1;
//四种情况都往外扩,情况2的1 2 只会执行一次就会失败,这样写是为了省略代码,不需要if else判断多种情况
while (i + pArr[i] < str.length && i - pArr[i] > -1) {
if (str[i + pArr[i]] == str[i - pArr[i]]) {
pArr[i]++;
}
else {
break;
}
}
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
max = Math.max(max, pArr[i]);
}
/*
* 返回值s字符串的最大回文长度 等于改造的字符串的最大回文半径 - 1
* eg.
* 1221 ==> #1#2#2#1# 返回值为 5-1 = 4
* 12321 ==> #1#2#3#2#1# 返回值为 6-1 = 5
*/
return max - 1;
}
public static void main(String []args) {
String s1 = "123";
String s2 = "ab43534c1234321ab";
System.out.println("s1: " + maxLengthOfPalindrome(s1));
System.out.println("s2: " + maxLengthOfPalindrome(s2));
}
}
窗口最大值更新结构
使用双端队列存索引,每次窗口右边界右移时,先看当前元素是否能加入双端队列尾部,不可以则弹出尾部元素直到可以加入,从而维持一个单调队列
双端队列维持的是当右边界不动,左边界右移的时候,依次判断是否跟队头元素相同从而弹出最大值
平均时间复杂度为O(1)
public class GetMaxNumInWindow {
public static int[] getMaxNumInWin(int[] arr, int win){
if(arr == null || arr.length < win || win < 1){
return null;
}
LinkedList<Integer> maxQueue = new LinkedList<>();
// 共有arr.length - win + 1个窗口,即共有arr.length - win + 1个最大值
int[] res = new int[arr.length - win + 1];
int index = 0;
for(int i = 0; i < arr.length; i++){
while(!maxQueue.isEmpty() && arr[maxQueue.peekLast()] <= arr[i]){
/**
* 如果当前双端链表中的最后一个值比arr[i]小,则将其弹出,直到找到比arr[i]大的数,将arr[i]放在其后面,
* 如果maxQueue中没有比arr[i]大的数,那就将maxQueue中的数全部弹出,arr[i]放在头部
*/
maxQueue.pollLast();
}
maxQueue.addLast(i);
if(maxQueue.peekFirst() == i - win){
// 只有当窗口形成后才会有从双端队列头部失效一个数,即过期还是没过期
maxQueue.pollFirst();
}
if(i >= win - 1){
// 至少有一个窗口存在时,才有max
res[index] = arr[maxQueue.peekFirst()];
}
}
return res;
}
}
单调栈
用途:想知道一个数左边比他大最近的是谁,右边比他大最近的是谁
应用
定义:正数数组中累积和与最小值的乘积,假设叫做指标A
给定一个数组,请返回子数组中,指标A最大的值
思路:以当前数字为最小值,求它的子数组,子数组选累加和最大的那个
由此可知是单调栈的做法