算法 马拉车 解决最长回文子串 和 双端队列保留窗口最大值 单调栈

Manacher 最长回文字串的个数

public class Code_01_Manacher {
    public static int maxPLen(String s){
        if(s == null || s.length() == 0){
            return 0;
        }
        char[] str = mancherString(s);//123->#1#2#3# 防止奇数偶数
        int[] pArr = new int[str.length];
        int R = -1;//第一个失败的位置,这个R前面都是正确的
        int C = -1;
        int max = Integer.MAX_VALUE;
        for (int i = 0; i < str.length; i++) {
            pArr[i] = i > R ? Math.min(pArr[2 * C - i],R-i):1;
            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.min(max,pArr[i]);
        }
        return max -1;//返回的是半径,如果是直径除2
    }
}

给定一个字符串,变成回文串,只能在后面添加回文串,后面添加最少
思路,找到第一个数的回文半径能到最右边,然后把前面的补上

    public static String maxPLens(String s){
        if(s == null || s.length() == 0){
            return 0;
        }
        char[] str = mancherString(s);//123->#1#2#3# 防止奇数偶数
        int[] pArr = new int[str.length];
        int R = -1;//第一个失败的位置,这个R前面都是正确的
        int C = -1;
        int maxContainsEnd = -1;
        for (int i = 0; i < str.length; i++) {
            pArr[i] = i > R ? Math.min(pArr[2 * C - i],R-i):1;
            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;
            }
            if(R == str.length){
                maxContainsEnd = pArr[i];
                break;
            }
        }
        char[] res = new char[s.length() - maxContainsEnd + 1];
        for (int i = 0; i < res.length; i++) {
            res[res.length-1-i] = str[i * 2 + 1];
        }
        return String.valueOf(res);
    }

假设一个固定大小为W的窗口,依次划过arr,返回每一个滑动窗口的最大值

    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[qmax.peekLast()] <= arr[i]){
                qmax.pollLast();
            }
            qmax.addLast(i);
            //如果窗口不够w个,就弹出
            if(qmax.peekFirst() == i - w){
                qmax.pollFirst();
            }
            //以上窗口更新完了,收答案
            if (i >= w - 1) {
                res[index++] = qmax.peekFirst();
            }
        }
        return res;
    }

给定一个整形数组arr,和一个整数sum,某个arr中的子数组sub,如果想要达标,必须满足:
sub中的最大值-sub的最小值 <=sum。返回arr中达标子数组的个数
思路:如果一个数组符合,这个数组的所有子数组也符合,如果一个数组不符合,这个数组增长之后也不符合。
设置两个窗口,一个保持最大值,一个保持最小值,从0下标往后增加,一直到不符合的位置,然后知道了0为下标的结果,然后窗口从1开始,但是保持原来的窗口,就是把原来窗口的起始位置0变为1,这样得到1为下标符合条件的结果,重复

   public static int getNum(int[] arr,int num){
        if(arr == null || arr.length == 0){
            return 0;
        }
        LinkedList<Integer> qmin = new LinkedList<>();
        LinkedList<Integer> qmax = new LinkedList<>();
        int L = 0;
        int R = 0;
        //窗口[L,R) [0,0)代表窗口一个数没没有 [0,1)表示只有0
        int res = 0;
        while(L < arr.length){
            while(R < arr.length){
                while(!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]){
                    qmin.pollLast();
                }
                qmin.addLast(R);
                while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]){
                    qmax.pollLast();
                }
                qmax.addLast(R);

                if(arr[qmax.getFirst()] - arr[qmin.getFirst()] > num){
                    break;
                }
                R++;
            }
            res += R - L;
            if(qmin.peekFirst() == L){ //找到这次以L开头的结果后。L窗口要往后移动一个单位
                qmin.pollFirst();
            }
            if(qmax.peekFirst() == L){
                qmax.pollFirst();
            }
            L++;
        }


        return res;
    }

单调栈,在一个数组中,这个数字,左边比这个数小且最近,右边的数比这个数小且最近
这个栈从顶到底部 保持从大到小排列,谁准备弹出,保存这个弹出左边最近且右边最近且最小的记录。

//返回数组的第一行代表 第一个位置左边距离它最小的 右边距离它最小的 
    public static int[][] getNearLess(int[] arr){
        int[][] res = new int[arr.length][2];

        //单调栈 存放下标组成的list 因为有重复的值 所以用list 
        Stack<List<Integer>> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) {
            //peek获取的是列表,要获取第一个0位置的元素
            while(!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]){
                List<Integer> popIs = stack.pop();
                int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size()-1);
                for(Integer popi : popIs){
                    res[popi][0] = leftLessIndex;
                    res[popi][1] = i;
                }
                
                //相等的 且比你小的
                if(!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]){
                    stack.peek().add(Integer.valueOf(i));
                }else{//不相等的 比你小的,或者栈为空 第一次进栈
                    ArrayList<Integer> list =new ArrayList<>();
                    list.add(i);
                    stack.push(list);
                }
            }
        }
        while(!stack.isEmpty()){
            List<Integer> popIs = stack.pop();
            int leftLessIndex = stack.isEmpty() ?  -1 : stack.peek().get(stack.peek().size());
            for(Integer popi : popIs){
                res[popi][0] = leftLessIndex;
                res[popi][1] = -1;
            }
        }
        return res;
    }

给定一个只包含正整数的数组arr,arr中任何一个子数组sub,一定可以算出(sub累加和)*(sub中的最小值)是什么,那么所有的子数组中,这个值最大是多少。
思路:找到以第一个位置为最小值的最长的数组,找到第二个位置最长的数组,,sub的累加和利用前缀和,前缀和就是从开始位置到当前位置的累加和, 根据前缀和可以求出任意长度数组的累加和,比如知道了1-L的和 知道了1-R的和 求L-R的累加和,用前面两个累加和相减就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值