一、Manacher算法解决的问题
字符串str中,最长回文子串的长度如何求解?如何做到时间复杂度O(N)完成?
基本概念:
回文半径, 回文串长度的一半
回文直径:回文串长度
回文半径数组
之前所到达的最右回文右边界R
取得更远边界时,中心点的位置C
二、算法过程
- 第一种情况:当来到某一个中心点时,没有在最右回文右边界中,暴力扩。
- 第二种情况:当来到某一个中心点时,在最右回文右边界中,当前位置关于中心点对称的位置i’、根据i’的回文半径进行求解。
三、代码实现
package ManacherTestAndOther;
public class TestManacher {
// 预处理,保证奇数和偶数的情况都能处理到
public char[] ProcessString(String str) {
char[] orignal = str.toCharArray();
char[] ret = new char[str.length() * 2 + 1];
int index = 0;
for(int i=0; i<ret.length; ++i) {
if((i & 1) == 1) {
ret[i] = orignal[index++];
}else {
ret[i] = '#';
}
}
return ret;
}
public String Manacher(String str) {
if(str == null || str.length() == 0) return null;
char[] s = ProcessString(str);
int[] radius = new int[s.length];
int C = -1;
int R = -1;
int Max = Integer.MIN_VALUE;
int ans = -1; // 记录最长回文串中心
for(int i=0; i<s.length; ++i) {
int len = i >= R? 1: Math.min(radius[2 * C - i], R - i); // 不需要进行判断一定是回文串的回文半径
int r = i + len;
int l = i - len;
while(l >= 0 && r < s.length) {
if(s[l] == s[r]) {
--l;
++r;
}else {
break;
}
}
if(r > R) {
R = r; // 更新最右回文半径
C = i; // 更新回文中心
}
radius[i] = r - i; // 记录回文半径
if(Max < radius[i]) {
Max = radius[i];
ans = i;
}
}
if(ans == 1) {
return ""+str.charAt(0)+" ( len ="+(Max - 1)+")";
}
StringBuilder sBuilder = new StringBuilder();
for(int i=ans - radius[ans]+1; i<ans + radius[ans]; ++i ) {
if(s[i] != '#') {
sBuilder.append(s[i]);
}
}
return sBuilder.toString()+" ( len ="+(Max - 1)+")";
}
public static void main(String[] args) {
TestManacher tm = new TestManacher();
String string = "1354513153"; // 51215
System.out.println(string + ":" +tm.Manacher(string));
string = "1345625";
System.out.println(string + ":" +tm.Manacher(string));
string = "abscsdkkdscsba";
System.out.println(string + ":" +tm.Manacher(string));
string = "dasdsaabscsdkkdscsbadfdda";
System.out.println(string + ":" +tm.Manacher(string));
string = "4d5asd5115s";
System.out.println(string + ":" +tm.Manacher(string));
string = "4d5asd515ds";
System.out.println(string + ":" +tm.Manacher(string));
}
}
运行结果
四、滑动窗口
题目:窗口内最大最小值问题
思路:利用双端队列维护一个递减的数组
package ManacherTestAndOther;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
public class MoveWindow {
public int[] windows(int[] nums, int w) {
if(nums == null || nums.length < w) {
return null;
}
Deque<Integer> qmax = new LinkedList<>(); // 双端队列
int[] ret = new int[nums.length - w + 1];
int index = 0;
for(int i=0; i<nums.length; ++i) {
while(!qmax.isEmpty() && nums[qmax.peekLast()] < nums[i]) { //维持队列中的递减顺序
qmax.pollLast();
}
qmax.offerLast(i);
if(qmax.peekFirst() == i - w) {
qmax.pollFirst();
}
if(i >= w - 1) {
ret[index++] = nums[qmax.peekFirst()];
}
}
return ret;
}
public static void main(String[] args) {
MoveWindow mw = new MoveWindow();
System.out.println(Arrays.toString(mw.windows(new int[] {4, 3, 5, 4, 3, 3, 6, 7}, 3)));
}
}
运行结果
五、单调栈
题目:单调栈
package ManacherTestAndOther;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class MonotonicityStack {
public int[][] MStack(int[] arr){
int[][] ret = new int[2][arr.length]; // 用于存储答案ret[0]左边小的数 ret[1]右边小的数
Arrays.fill(ret[0], Integer.MAX_VALUE);
Arrays.fill(ret[1], Integer.MAX_VALUE);
Stack<ArrayList<Integer>> stack = new Stack<>(); // ArrayList是放置出现相同的数字 递增栈
for(int i=0; i<arr.length; ++i) {
while(!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
List<Integer> list = stack.pop();
for(int num: list) {
ret[1][num] = arr[i]; // 右侧小的数值
}
}
if(!stack.isEmpty()) {
List<Integer> list = stack.peek();
if(arr[list.get(0)] == arr[i]) {
ret[0][i] = ret[0][list.get(0)]; // 左边小于的和前面相等
list.add(i); // 加入当前同值队列
}else {
ret[0][i] = arr[list.get(list.size() - 1)]; // 左边最小值
ArrayList<Integer> ele = new ArrayList<>(); // 加入栈中
ele.add(i);
stack.push(ele);
}
}else {
ArrayList<Integer> ele = new ArrayList<>(); // 加入栈中
ele.add(i);
stack.push(ele);
}
}
return ret;
}
public static void main(String[] args) {
MonotonicityStack ms = new MonotonicityStack();
int[] nums = new int[] {1, 5, 6, 7, 2, 0, 5, 7 ,5, 8};
int[][] ans = ms.MStack(nums);
System.out.println("原数组:"+Arrays.toString(nums));
for(int i=0; i<nums.length; ++i) {
System.out.print("左部最近的小于"+nums[i]+"的数:");
System.out.print(ans[0][i] == Integer.MAX_VALUE? "无": ans[0][i]);
System.out.print(" 右部最近的小于"+nums[i]+"的数:");
System.out.println(ans[1][i] == Integer.MAX_VALUE? "无": ans[1][i]);
}
}
}
运行结果
题目:
利用单调栈进行
package ManacherTestAndOther;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class TheMaxIndexA {
public int[] Process(int[] nums) {
int[][] bounds = new int[2][nums.length];
Arrays.fill(bounds[0], -1);
Arrays.fill(bounds[1], nums.length);
int max = Integer.MIN_VALUE;
Stack<ArrayList<Integer>> stack = new Stack<>();
for(int i=0; i<nums.length; ++i) { // 以每个数为最小值,找到子数组的边界
while(!stack.isEmpty() && nums[stack.peek().get(0)] > nums[i]) {
List<Integer> list = stack.pop();
for(int num: list) {
bounds[1][num] = i; // 设置右边界
}
}
if(!stack.isEmpty()) {
List<Integer> list = stack.peek();
if(nums[list.get(0)] == nums[i]) {
list.add(i);
bounds[0][i] = bounds[0][list.get(0)];
}else {
ArrayList<Integer> ele = new ArrayList<>();
ele.add(i);
bounds[0][i] = list.get(list.size() - 1);
stack.push(ele);
}
}else {
ArrayList<Integer> ele = new ArrayList<>();
ele.add(i);
stack.push(ele);
}
}
int ans = -1; //用于记录答案
for(int i=0; i<nums.length; ++i) { // 计算最大指标A的值
int temp = getNum(nums, bounds[0][i], bounds[1][i], nums[i]);
if(max < temp) {
max = temp;
ans = i;
}
}
return new int[] {max, bounds[0][ans], bounds[1][ans], nums[ans]};
}
/*
* 获取子数组指标A的值
* num数组
* l 子数组左边界
* r 子数组右边界
* min 子数组中的最小值
*/
public int getNum(int[] num, int l, int r, int min) {
int sum = 0;
for(int i=l+1; i<r; ++i) {
sum += num[i];
}
return min * sum;
}
public static void main(String[] args) {
TheMaxIndexA indexA = new TheMaxIndexA();
int[] arr = new int[] {1, 5, 6, 7, 2, 0, 5, 7 ,5, 8};
int[] ans = indexA.Process(arr);
System.out.println("原数组为:"+Arrays.toString(arr));
System.out.println("指标A的最大值为:"+ans[0]);
System.out.print("子数组为:");
for(int i=ans[1]+1; i<ans[2]; ++i) {
System.out.print(arr[i]+" ");
}
System.out.println();
System.out.println("其中最小值为:"+ans[3]);
}
}
运行结果