目录
manacher
找一个字符串中的最长回文子串: 不加标志字符遍历字符串的每一位向两边扩,只能得到奇数位,无法得到偶数位的回文子串 加上标志字符后就可以找到每个回文子串,所得值/2就是原始长度 在左右扩比较的过程中,实的和实的比,虚的和虚的比,标志字符任意取不会影响 当字符串全相同时,此为最差情况O(N^2)
Manacher(O(N)): 1、当前中心没在最右回文右边界时暴力扩充 2、当前中心在最右回文右边界,就可以得到关于C对称的点,当前中心左边的所有点的最长回文半径已经保存在数组中: (1)对称点的回文区域在L~R内,当前中心的最长回文半径就是对称点的最长回文半径 (2)对称点的回文区域超过了最右回文右边界对称的最左回文左边界,当前中心的最长回文半径就是中心点到最右回文右边界 (3)对称点的回文区域的左边界正好是最右回文右边界对称的最左回文左边界,当前中心的回文半径至少是对称点的回文半径还需要扩
package com.wtp.基础提升.manacher; public class manacher { public static void main(String[] args) { System.out.println(getManacherString("123".toCharArray())); } public static char[] getManacherString(char[] chs) { char[] res = new char[chs.length * 2 + 1]; int index = 0; for(int i = 0;i < res.length;i++) { res[i] = (i & 1) == 0 ? '#' : chs[index++]; } return res; } public static int geMax(char[] s) { if(s == null || s.length == 0) { return 0; } char[] str = getManacherString(s); int[] res = new int[str.length]; int max = Integer.MIN_VALUE; int C = -1; int R = -1; for(int i = 0;i < str.length;i++) { //R最右回文右边界下一个位置 //R > i 就有三种情况 i`边界在R内、i`边界在R外、i`边界压在最右边界 //R <= i i在R外 不用验的只有自己 res[i] = R > i ? Math.min(res[2*C-i], R - i) : 1; while(i + res[i] < str.length && i - res[i] > -1) { if(str[i + res[i]] == str[i - res[i]]) { res[i]++; }else { break; } } if(i + res[i] > R) { R = i + res[i]; C = i; } max = Math.max(max, res[i]); } return max - 1; } }
滑动窗口
滑动窗口求窗口内最大值: 维持一个双端队列,保持从头到尾从大到小,头部是最大值。R右移动时加入,加入时从尾部加入,若加入时为空或者小于队尾就直接加入,否则一直弹出,相同时也弹出。 L右移时信息过期,如果过期的信息是窗口中最大就从头弹出,否则不用管。 双端队列维护的是当R不动L动时,当前窗口最大值的优先级。值大的下标还大的晚过期所以加入时从队尾弹出不可能是最大值的值,值相同但下标大也移除保存的信息更持久 每个位置都进出一次,更新双端队列n个数的总代价是O(N)平均代价O(1)
package com.wtp.基础提升.滑动窗口; import java.util.LinkedList; public class Window {//维持窗口内最大值 private int L; private int R; private int[] arr; private LinkedList<Integer> qmax; public Window(int[] a) { arr = a; L = -1; R = 0; qmax = new LinkedList<>(); } public void addNumFromRight() { if(R == arr.length) { return; } while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) { qmax.pollLast(); } qmax.add(R); R++; } public void removeNumFromLeft() { if(L >= R-1) { return; } L++; if(qmax.peekFirst() == L) { qmax.pollFirst(); } } public Integer getMax() { if(!qmax.isEmpty()) { return arr[qmax.peekFirst()]; } return null; } public static int[] getMaxWindow(int[] arr,int w) { if(arr == null || w < 1 || arr.length < w) { return null; } LinkedList<Integer> qmax = new LinkedList<>(); int[] res = new int[arr.length - w + 1]; int index = 0; for(int i = 0;i < arr.length;i++) { while(!qmax.isEmpty() && arr[i] <= arr[qmax.peekLast()]) { qmax.pollLast(); } qmax.add(i); if(qmax.peekFirst() == i - w) { qmax.pollFirst(); } if(i >= w - 1) { res[index++] = arr[qmax.peekFirst()]; } } return res; } }
单调栈
统计数组中每个数左边比当前数大(小)的是什么,右边比当前数大(小)的是什么。时间复杂度O(N)
单调栈求数组中每个数左边右边最近的大的值: 保持从栈底到栈顶从大到小。加入数时若待加入数大于栈顶数就开始弹出,弹出时生成信息,左边大的就是底下的数,右边大的就是待加入数。 若遍历完后栈中还有数就进行清算,右边没有数,左边数是压的数。 每一个数都是进一次出一次,时间复杂度O(N)
package com.wtp.基础提升.单调栈; import java.util.LinkedList; import java.util.List; import java.util.Stack; public class 求数组左右最近大的数 { public static void main(String[] args) { //{5,4,6,7,2,3,0,1} int[] arr = {1,2,3,4,5,6,7,8,9}; process2(arr); } //数组中无重复数 //找左右最近大的数 维持栈底到栈顶从大到小 //找左右最近小的数 维持栈底到栈顶从小到大 public static void process(int[] arr) { if(arr == null || arr.length == 0) { return; } Stack<Integer> stack = new Stack<>(); for(int i = 0;i < arr.length;i++) { //如果加入的数比栈顶的数大 无法维持底->顶从大到小 就弹出结算 //弹出的数的左边最近大的数为当前栈的栈顶 右边最近大的数为要加入的数 while(!stack.isEmpty() && arr[stack.peek()] < arr[i]) { int cur = stack.pop(); //如果弹出元素后栈中无元素 说明弹出元素(要结算的元素)的左边没有大的了 print(cur,arr[cur], !stack.isEmpty() ? arr[stack.peek()] + "" : "无", arr[i] + ""); } stack.push(i); } //如果栈中还有元素就结算 右边没有大的了 while(!stack.isEmpty()) { int cur = stack.pop(); print(cur,arr[cur],!stack.isEmpty() ? arr[stack.peek()] + "":"无","无"); } } public static void print(int index,int target,String left,String right) { System.out.println("下标"+ index + "->" + target + " 左边:" + left + " 右边:" + right); } //有重复值 public static void process2(int[] arr) { if(arr == null || arr.length == 0) { return; } //相同的值的下标放在一起 从后加入双端队列(LinkedList) Stack<LinkedList<Integer>> stack = new Stack<>(); for(int i = 0;i < arr.length;i++) { while(!stack.isEmpty() && arr[stack.peek().peekLast()] < arr[i]) { LinkedList<Integer> cur = stack.pop(); while(!cur.isEmpty()) { int index = cur.removeLast(); print(index,arr[index], !stack.isEmpty() ? arr[stack.peek().peekLast()] + "" : "无", arr[i] + ""); } } if(!stack.isEmpty() && arr[stack.peek().peekLast()] == arr[i]) { stack.peek().addLast(i);//将重复下标加入到双端队列末尾 }else { LinkedList<Integer> list = new LinkedList<>(); list.add(i); stack.push(list); } } while(!stack.isEmpty()) { LinkedList<Integer> cur = stack.pop(); while(!cur.isEmpty()) { int index = cur.removeLast(); print(index,arr[index], !stack.isEmpty() ? arr[stack.peek().peekLast()] + "" : "无", "无"); } } } }