算法基础提升

布隆过滤器

集合只有加入和查询,用较小的空间。
如:有100亿条url黑名单比较当前网站是否在其中
允许有失误,不在过滤器里面,误报成在。

创建m bit长度的数组。对每个url通过不同hash函数的值并%上m,获得k个值,在m数组上涂黑,当碰到已经涂黑的不管。
查的时候,对要查的url同样操作,对获得的值比较,如果都涂黑则一定在里面。
n为样本量,p为失误率
n为样本量,p为失误率
在这里插入图片描述

并行解决岛问题(并查集)

在矩阵中有每个位置和上下左右相连,如果都唯一为一个岛,求一个矩阵有多少个岛?

并查集
只提供判断是否是同一个集合合并两个集合,两个功能。

先给出所有集合,让每个集合指向自己。
判断是否同一集合,比较两元素最高指向:顶,是否相同。
合并两个集合,让数量少的顶指向多的顶。

优化:扁平化,把所有元素都指向顶,节省查找时间

解决
把大的切除后分别并行,记录递归起始点和边界点。
一共有多少起始点有多少集合,把相邻的边界点做并查集。

kmp算法

字符串str1,str2,如果str1包含str2返回开始位置。

next数组
str2的每个字符最长前缀和后缀的长度

[a,a,b,a,a,b,s,a,a,b,a,a,b,s,t]
规定第0个为-1,第一个为0
所以每个长度为
[-1,0,1,0,1,2,3,...]

解决
str1从i开始,str2从0开始比较。
当str1为x位置时,str2为y位置时匹配不成功。
标准:str1回退到i+1,str2回退到0.
kmp:
因为,知道str2的前缀和后缀想同长度k,所以str1,x位置前k位置一定和str2前k位置相同,所以str2只需要后退到k+1比较就行。
在这里插入图片描述
如果不等,就继续后退
next数组
求第 i 位置的最大长度
1、获得 i-1 位置的长度
2、比较 i-1 和 i-1 的最大长度的前半截的后一位 k
3、如果 k=i-1 则 i 的长度为 next[i-1]+1
4、如果不等,则利用 k 位的最大长度前半截的后一位 j 和 i-1 比较。
在这里插入图片描述

public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABDABDE";
        String str2 = "AB A";
        int[] next = getNext(str2);
        System.out.println(Arrays.toString(next));
        char[] l1 = str1.toCharArray();
        char[] l2 = str2.toCharArray();
//        one:str1当前位置,two:str2当前位置
        int one=0,two=0;
        while (one<l1.length && two<l2.length){
            if (l1[one] == l2[two]){
                one++;
                two++;
//            如果two在首位也匹配不了,则str1前进
            }else if (next[two] == -1){
                one++;
//            two还能前跳,继续跳
            }else {
                two = next[two];
            }
        }
        int star = two==l2.length?one-two:-1;
        System.out.println(star);
    }


//    获得next数组
    public static int[] getNext(String str2){
        int[] next = new int[str2.length()];
        char[] array = str2.toCharArray();
        next[0] = -1;
        next[1] = 0;
        int i = 2;
//        前一个next的长度
        int c = 0;
//        全部找一遍
        while (i<next.length){
//            如果前一个位置和前一个位置最大长度的后一个比较
            if (array[i-1] == array[0]){
//                相等则当前位置的长度为前一个位置加一,i加一
                c+=1;
                next[i] = c;
                i+=1;
//            如果c大于0,说明前一个还有相同的继续找
            }else if (c>0){
//                把c变为前一个的前一个
                c = next[c];
//            前面没有想的只有重新开始
            }else {
                next[i++] = 0;
            }
        }
        return next;
    }

manacher算法

求最长回文

abba
abcba
都是回文

求a121b回文
经典:
1、进行扩容:a#1#2#1#b
2、对每位进行两边对比

manachaer:
1、x 没有在最右回文范围里,暴力扩
2、x 在最右回文里,根据最右回文中心点,找到对称点 i ,对称点之前求过回文区域
2.1、对称点回文区域在最右回文区域内,那么x回文区域大小就为i的回文区域
2.2、i的回文区域超过最右回文,x回文区域长度为 最右回文-i回文
2.3、i的回文与最右回文压线,x回文一部分是i,再可以从最右回文右边开始暴力扩

        String s = "acbahkafojlnavnufnavkvnv";
//        String s = "abba";
        String kZS = getKuoZhanS(s);
        System.out.println(getPArr(s));
//        左程云老师的
        System.out.println(maxLcpsLength(s));


    }

    //  获得扩展后的字符串
    public static String getKuoZhanS(String s) {
        StringBuilder sbuider = new StringBuilder();
        sbuider.append("#");
        for (int i = 0; i < s.length(); i++) {
            sbuider.append(s.substring(i, i + 1));
            sbuider.append("#");
        }
        return sbuider.toString();
    }

    public static int getPArr(String s) {
        String kZS = getKuoZhanS(s);
//        每个位置的长度数组
        int[] pArr = new int[kZS.length()];
//        R:最大半径到达位置,C:最大半径中心,max:最大半径
        int R = -1, C = -1,max=Integer.MIN_VALUE;
        for (int i = 0; i < kZS.length(); i++) {
//            中心在最大半径外,暴力扩
            if (i >= R) {
                int l = getBLKR(kZS, i, i + 1);
                pArr[i] = l;
                if (l + i > R) {
                    R = l + i;
                    C = i;
                }
                //            中心在最大半径内
            } else {
//              对于最大半径中心对称的点i`的半径在最大半径边上,除了i-pArr[dCD]-1和i+pArr[dCD]+1开始暴力扩
                int dCD = 2 * C - i;
                if (dCD - pArr[dCD] == C - pArr[C]) {
                    int l = getBLKR(kZS, i, i + pArr[dCD]) + pArr[dCD];
                    pArr[i] = l;
                    if (l + i > R) {
                        R = l + i;
                        C = i;
                    }
//                    不需要暴力扩
                }else {
                    pArr[i] = Math.min(R-i-pArr[dCD],pArr[dCD]);
                }
            }
            max = Math.max(max, pArr[i]);
        }
        return max;

    }

    //    暴力扩获得半径
    public static int getBLKR(String kZS, int center, int index) {
//        左边需要对比的位置
        int left = center - index + center;
//        长度
        int l = 0;
//        如果右边对比位置超出扩展长度,左边位置小于0
        while (index < kZS.length() && left >= 0) {
            if (kZS.substring(left, left + 1).equals(kZS.substring(index, index + 1))) {
                index++;
                l++;
            } else {
                break;
            }
            left = center - index + center;
        }
        return l;
    }

    public static int maxLcpsLength(String s) {
        if (s == null && s.length() == 0) {
            return 0;
        }
        char[] kZS = getKuoZhanS(s).toCharArray();
        int[] pArr = new int[kZS.length];
        int C = -1, R = -1, max = Integer.MIN_VALUE;
        for (int i = 0; i < kZS.length; i++) {
            pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
            while (i + pArr[i] < kZS.length && i - pArr[i] > -1) {
                if (kZS[i + pArr[i]] == kZS[i - pArr[i]]) {
                    pArr[i]++;
                } else {
                    break;
                }
            }
            if (i + pArr[i] > R) {
                R = i + pArr[i];
                C = i;
            }
            max = Math.max(max, pArr[i]);
        }
        return max - 1;
    }

窗口问题

给数组长度为n,窗口大小为w,窗口每次向右滑1,求每次窗口内的最大值数组?

数组为:[4,3,5,4,3,3,6,7]
窗口大小为3
最大值数组为:[5,5,5,4,6,7]

用双端队列。求最大值则左到右,大到小
R每向右移动,把该位置的下表移动到队列中,严格按照大到小,比R位置上小的都弹出,相同也弹。再放入。
L向右动,比较前一位置的下标是不是队首,是弹出。

单调栈

求i位置左边离他最近的大的数,右边比他大离他近的数。

栈为从底到上大到小。
每次放入坐标,如果放入的数比栈顶大,则弹出。弹出时生成数据。
弹出i位置,左边离你最近的大的数为弹出下面的位置,右边最近大的数为当前位置。
如果遍历玩,栈中还有数据,则单独出栈。

如果有重复值,则把重复的下表压在一起,左边离他最近最大的为最右边的下标

树形dp套路

从二叉树的节点a出发,沿途节点只能经过一次,到达节点b是路径上的节点个数叫做a到b的距离,求整颗数的最大距离
1、x不参与
左最大距离
右最大距离
2、x参与
左高+1+右高

三者之间取最大值
在这里插入图片描述
在这里插入图片描述

Morris遍历

一种二叉树的遍历方式,时间复杂度为O(N),额外空间复杂度为O(1)。
在这里插入图片描述
在这里插入图片描述
先序遍历
在这里插入图片描述
中序遍历
在这里插入图片描述
后序遍历

第二次回到car逆序打印左树右边界,再单独逆序打印整个右边界

在这里插入图片描述
在这里插入图片描述

区间过滤

32位的无符号整数范围是0~4294967295,现有一个包含40亿个符号整数的文件,内存限制为3KB,求一个没出现过的数。

最多能够申请3KB/4 ~ 最多2的9次方512。所以可以申请int[512]数组,把2的32次方分为512份,a = 2的32次方 / 512,40亿每位都除以a,得几就把数组上该位+1,因为40亿数全部在2的32次方里,如果哪位不够a则说明在该范围内差数,在该范围再分成512份,继续过40亿数,直到精确到个。

二维堆

某搜索公司一天的用户词汇搜索是海量的,请设计出一种求出每天热门top100词汇的可行方法。

用哈希把词汇分配到不同的堆中并统计次数,再利用总堆把每个堆的头词汇放入总堆中,每次弹出头部,并在堆中删除相应词汇,再重新把该堆中的头部放入总堆。总堆持续弹出100次。

无运算符运算

给定a,b执行加减乘除会导致内存溢出,那么你给定函数不必对此负责,除此之外保证计算过程不溢出。


两数相加的和等于两数异或a和两数相与向左移一位b的和,用a,b继续异或和与左移,当不能与左移位时,结果就为当前异或结果。
在这里插入图片描述

减法
a加b的相反数
在这里插入图片描述

乘法
在这里插入图片描述

动态规划

有1-N个位置,当前位置为K,要求在X步内移动到M位置,不能停止不动,求有多少步?

暴力递归
在这里插入图片描述
因为当后两个值确定,返回值一定确定,在暴力过程中会重复递归,可以用空间换时间
在这里插入图片描述
计划递归
在这里插入图片描述
严格表结构

下一步优化表的建立,根据动态递归的条件可以判断出当
以横坐标为当前位置,竖坐标为剩余步数的表。
当前位置为1时不可能到达0位置,当前的值为右上的值,最右边的值为左上的值,之间的值为左上+右上
在这里插入图片描述
在这里插入图片描述

AVL树

任何一个节点左右两个节点相差不超过1
利用左旋和右旋
当加入一个节点,检测从该节点往上具不具备平衡性
当删除一个节点,从顶替该节点的上一个节点开始查

SB树

在这里插入图片描述
在这里插入图片描述
平衡后谁的孩子节点变化了继续调
在这里插入图片描述

跳表

在这里插入图片描述
跳表在插入时会随机获得层数,如图要插入70,获得层数2层。
找到最右边最高层数的小于等于70的值,为20,层数为5
70没有5层,所以20降一层,最右节点变成50,再降到2层,把50第二层指向70,70指向100

找规律

在这里插入图片描述
在这里插入图片描述

等概率返回

一个函数f,可以1~5等概率返回,请加工出1-7的数字等概率返回的函数g

设定函数如果f返回的数小于3则返回0,大于3返回1,等于3重新f。
返回0-7只要3个二进制位,得到的数+1就是等概率,位7重试

在这里插入图片描述

函数f,可以等概率返回a-b的一个,加工出c-d的数字概率函数g

思路一样,提供13-21,返回30-59
把f加工成0和1返回器,17以下返回0以上返回1,21重试
0-29为5位二进制位,拼起来

函数f,以p概率返回0,1-p返回0,加工出等概率返回0和1的函数g

加工返回0和1,揉两回,得到【0,1】和【1,0】可以返回0或1

求有效括号

定义dp数组,dp[i]为以当前位置为尾的长度。
如果为左括号则为0,
如果为右括号则看dp[i-1]的值,往前推dp[i-1]个长度,到p位置,
如果为右括号为0
如果为左括号,则再加dp[-1]的前一个位置的值
在这里插入图片描述
在这里插入图片描述

IndexTree(数组移动问题)

两种颜色的球,蓝色和红色,按1~n编号,共计2n个,为方便红球编号为负,蓝球不变,并打乱顺序。要求同一颜色的球升序排序,交换相邻两球,求最少操作次数。
[3,-3,1,-4,2,-2,-1,4] 最终交换结果为[1,2,3,-1,-2,-3,-4,4] 最少交换为10次

伪代码

   /**
     * @param lasta  最后蓝色球的数
     * @param lastb  最后红色球的数
     * @param arr    原始数组
     * @return
     */
    public static int zuo(int lasta,int lastb, int[] arr){
        //蓝色和红色的球数都为0,说明排序已经全部完成
        if (lasta==0 && lastb==0){
            return 0;
        }
        //lasta已经全部移动完毕
        if (lasta==0){
            int curCost = lastb来到此时最后位置的代价;
            int next = zuo(lasta, lastb-1, arr);
            return curCost+next;
        }
        //lastb已经全部移动完毕
        if (lastb==0){
            int curCost = lasta来到此时最后位置的代价;
            int next = zuo(lasta-1, lastb, arr);
            return curCost+next;
        }
        //都没有,中间位置
        int p1=Integer.MAX_VALUE,p2=Integer.MAX_VALUE;
        //lasta来到最后
        int lastaComeHost = lasta来到此时最后位置的代价;
        int next1 = zuo(lasta-1, lastb, arr);
        p1 = lastaComeHost+next1;
        //lastb来到最后
        int lastbComeHost = lastb来到此时最后位置的代价;
        int next2 = zuo(lasta-1, lastb, arr);
        p2 = lastbComeHost+next2;
        return Math.min(p1,p2);
    }

计算来到当前位置的代价利用indexTree
indextree为数组长度的全为1的数组。
计算从x位置到y位置的代价,即计算x~y位置1的个数减1:2到4代价为3-1 = 2。并把2设为0.
这样原数组不用改变。之前改变1为0,计算1个数时已经变了。
例如:

【2,1,-3,-1】 最后位置为3,indextree为【1,1,1,1】
第一轮: 2到3 【2,1,-1,-3】 移动  sum(2 ~ 3) - 1 = 1  indextree【1,1,0,1】
第二轮: 3到2  【2,1,-1,-3】 移动 sum(3 ~ 2)-1 = 0    indextree 【1,1,0,0】
第三轮: 0到1 【1,2,-1,-3】 移动 sum(0 ~ 1) -1 = 1 indextree 【0,1,0,0】
同理 一共移动2步 ,不用移动数组只要改变indextree即可
    public static int zuo(int lasta, int lastb,
                          IndexTree it, //支持快速查找距离
                          int end, // 不变永远 n-1
                          HashMap<Integer,Integer> map // 位置表
    ){
        //蓝色和红色的球数都为0,说明排序已经全部完成
        if (lasta==0 && lastb==0){
            return 0;
        }
        int p1=Integer.MAX_VALUE,p2=Integer.MAX_VALUE;
        int index,cost,next;
        if (lastb!=0){
            index = map.get(lastb);
            cost = it.sum(index,end)-1;
            it.add(index,-1);
            next = zuo(lasta, lastb-1, it, end-1, map);
            it.add(index,1);
            p1=cost+next;
        }

        if (lasta!=0){
            index = map.get(lasta);
            cost = it.sum(index,end)-1;
            it.add(index,-1);
            next = zuo(lasta-1, lastb, it, end-1, map);
            it.add(index,1);
            p2=cost+next;
        }

        return Math.min(p1,p2);
    }

只有lasta和lastb变换可以改成动态规划

位图

int set=0
表示32位

set |= 1 << 0
表示把第0位变成一

除法向上取整(a+b-1)/b

计算有几个1

1、Integer.bitCount(set)

2、循环检查二进制位

        int ret = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & (1 << i)) != 0) {
                ret++;
            }
        }
        return ret;

3、位运算优化

        int ret = 0;
        while (n != 0) {
            n &= n - 1;
            ret++;
        }
        return ret;

在这里插入图片描述

   //names : 用户数组。查询shiyans:实验数组
    public void zuo(int[][] names, int[][] shiyans) {
        int n = names.length, m = shiyans.length;
        //创建位图每个实验有n人
        int[][] sets = new int[m][(n + 31) / 32];
        //遍历用户数组修改sets位图,建立m号实验每个对应n个人位图
        for (int i = 0; i < n; i++) {
            for (int j : names[i]) {
                sets[j][i / 32] |= 1 << (i % 32);
            }
        }
        int[] ans = new int[shiyans.length];
        //每次查询
        for (int i=0;i<shiyans.length;i++){
            //一次查询的实验集合第一次查{1,2,3}实验人数
            int[] shiyan = shiyans[i];
            int count = 0;
            //把每个实验的位图取出来进行|运算求1,每个实验有(n+31)/32位
            for (int j=0;j<(n+31)/32;j++){
                int set=0;
                //第k个实验
                for (int k : shiyan) {
                    // |运算上第k个实验的第j位:j=0  0-31位同学有多少
                    set |= sets[k][j];
                }
                //统计(k*32) ~ (k+1)*32-1 个去重同学有多少
                count+=Integer.bitCount(set);
            }
            ans[i] = count;
        }
    }

差分数组

有n位数组,设:n=4:[0,0,0,0]
有f操作f[i] = [1,3,2]:1到3位加2:[2.2.2.0]
求每位的数字和。

可以用差分数组,添加第0位和n+1位数组: [0,0,0,0,0,0]
执行1到3位加时,第一位加,第m+1位-。即:[0,2,0,0,-2,0]

统计是结果为前一位累加和:[0,2+0,2+0,2+0,0 (-2+2),0]

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值