packageclass041;// 求最大公约数、最小公倍数publicclassCode01_GcdAndLcm{// 证明辗转相除法就是证明如下关系:// gcd(a, b) = gcd(b, a % b)// 假设a % b = r,即需要证明的关系为:gcd(a, b) = gcd(b, r)// 证明过程:// 因为a % b = r,所以如下两个等式必然成立// 1) a = b * q + r,q为0、1、2、3....中的一个整数// 2) r = a − b * q,q为0、1、2、3....中的一个整数// 假设u是a和b的公因子,则有: a = s * u, b = t * u// 把a和b带入2)得到,r = s * u - t * u * q = (s - t * q) * u// 这说明 : u如果是a和b的公因子,那么u也是r的因子// 假设v是b和r的公因子,则有: b = x * v, r = y * v// 把b和r带入1)得到,a = x * v * q + y * v = (x * q + y) * v// 这说明 : v如果是b和r的公因子,那么v也是a的公因子// 综上,a和b的每一个公因子 也是 b和r的一个公因子,反之亦然// 所以,a和b的全体公因子集合 = b和r的全体公因子集合// 即gcd(a, b) = gcd(b, r)// 证明结束publicstaticlonggcd(long a,long b){return b ==0? a :gcd(b, a % b);}publicstaticlonglcm(long a,long b){return(long) a /gcd(a, b)* b;}}
packageclass041;// 一个正整数如果能被 a 或 b 整除,那么它是神奇的。// 给定三个整数 n , a , b ,返回第 n 个神奇的数字。// 因为答案可能很大,所以返回答案 对 10^9 + 7 取模 后的值。// 测试链接 : https://leetcode.cn/problems/nth-magical-number/publicclassCode02_NthMagicalNumber{publicstaticintnthMagicalNumber(int n,int a,int b){long lcm =lcm(a, b);long ans =0;// l = 0// r = (long) n * Math.min(a, b)// l......rfor(long l =0, r =(long) n *Math.min(a, b), m =0; l <= r;){m =(l + r)/2;// 1....mif(m / a + m / b - m / lcm >= n){ans = m;r = m -1;}else{l = m +1;}}return(int)(ans %1000000007);}publicstaticlonggcd(long a,long b){return b ==0? a :gcd(b, a % b);}publicstaticlonglcm(long a,long b){return(long) a /gcd(a, b)* b;}}
packageclass041;importjava.math.BigInteger;// 加法、减法、乘法的同余原理// 不包括除法,因为除法必须求逆元,后续课讲述publicclassCode03_SameMod{// 为了测试publicstaticlongrandom(){return(long)(Math.random()*Long.MAX_VALUE);}// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果publicstaticintf1(long a,long b,long c,long d,int mod){BigInteger o1 =newBigInteger(String.valueOf(a));// aBigInteger o2 =newBigInteger(String.valueOf(b));// bBigInteger o3 =newBigInteger(String.valueOf(c));// cBigInteger o4 =newBigInteger(String.valueOf(d));// dBigInteger o5 = o1.add(o2);// a + bBigInteger o6 = o3.subtract(o4);// c - dBigInteger o7 = o1.multiply(o3);// a * cBigInteger o8 = o2.multiply(o4);// b * dBigInteger o9 = o5.multiply(o6);// (a + b) * (c - d)BigInteger o10 = o7.subtract(o8);// (a * c - b * d)BigInteger o11 = o9.add(o10);// ((a + b) * (c - d) + (a * c - b * d))// ((a + b) * (c - d) + (a * c - b * d)) % modBigInteger o12 = o11.mod(newBigInteger(String.valueOf(mod)));if(o12.signum()==-1){// 如果是负数那么+mod返回return o12.add(newBigInteger(String.valueOf(mod))).intValue();}else{// 如果不是负数直接返回return o12.intValue();}}// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果publicstaticintf2(long a,long b,long c,long d,int mod){int o1 =(int)(a % mod);// aint o2 =(int)(b % mod);// bint o3 =(int)(c % mod);// cint o4 =(int)(d % mod);// dint o5 =(o1 + o2)% mod;// a + bint o6 =(o3 - o4 + mod)% mod;// c - dint o7 =(int)(((long) o1 * o3)% mod);// a * cint o8 =(int)(((long) o2 * o4)% mod);// b * dint o9 =(int)(((long) o5 * o6)% mod);// (a + b) * (c - d)int o10 =(o7 - o8 + mod)% mod;// (a * c - b * d)int ans =(o9 + o10)% mod;// ((a + b) * (c - d) + (a * c - b * d)) % modreturn ans;}publicstaticvoidmain(String[] args){System.out.println("测试开始");int testTime =100000;int mod =1000000007;for(int i =0; i < testTime; i++){long a =random();long b =random();long c =random();long d =random();if(f1(a, b, c, d, mod)!=f2(a, b, c, d, mod)){System.out.println("出错了!");}}System.out.println("测试结束");System.out.println("===");long a =random();long b =random();long c =random();long d =random();System.out.println("a : "+ a);System.out.println("b : "+ b);System.out.println("c : "+ c);System.out.println("d : "+ d);System.out.println("===");System.out.println("f1 : "+f1(a, b, c, d, mod));System.out.println("f2 : "+f2(a, b, c, d, mod));}}
42. (必备)对数器打表找规律的技巧
packageclass042;// 有装下8个苹果的袋子、装下6个苹果的袋子,一定要保证买苹果时所有使用的袋子都装满// 对于无法装满所有袋子的方案不予考虑,给定n个苹果,返回至少要多少个袋子// 如果不存在每个袋子都装满的方案返回-1publicclassCode01_AppleMinBags{publicstaticintbags1(int apple){int ans =f(apple);return ans ==Integer.MAX_VALUE?-1: ans;}// 当前还有rest个苹果,使用的每个袋子必须装满,返回至少几个袋子publicstaticintf(int rest){if(rest <0){returnInteger.MAX_VALUE;}if(rest ==0){return0;}// 使用8规格的袋子,剩余的苹果还需要几个袋子,有可能返回无效解int p1 =f(rest -8);// 使用6规格的袋子,剩余的苹果还需要几个袋子,有可能返回无效解int p2 =f(rest -6);p1 += p1 !=Integer.MAX_VALUE?1:0;p2 += p2 !=Integer.MAX_VALUE?1:0;returnMath.min(p1, p2);}publicstaticintbags2(int apple){if((apple &1)!=0){return-1;}if(apple <18){if(apple ==0){return0;}if(apple ==6|| apple ==8){return1;}if(apple ==12|| apple ==14|| apple ==16){return2;}return-1;}return(apple -18)/8+3;}publicstaticvoidmain(String[] args){for(int apple =0; apple <100; apple++){System.out.println(apple +" : "+bags1(apple));}}}
packageclass044;// 用固定数组实现前缀树,空间使用是静态的。推荐!// 测试链接 : https://www.nowcoder.com/practice/7f8a8553ddbf4eaab749ec988726702b// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.util.Arrays;publicclassCode02_TrieTree{// 如果将来增加了数据量,就改大这个值publicstaticintMAXN=150001;publicstaticint[][] tree =newint[MAXN][26];publicstaticint[] end =newint[MAXN];publicstaticint[] pass =newint[MAXN];publicstaticint cnt;publicstaticvoidbuild(){cnt =1;}publicstaticvoidinsert(String word){int cur =1;pass[cur]++;for(int i =0, path; i < word.length(); i++){path = word.charAt(i)-'a';if(tree[cur][path]==0){tree[cur][path]=++cnt;}cur = tree[cur][path];pass[cur]++;}end[cur]++;}publicstaticintsearch(String word){int cur =1;for(int i =0, path; i < word.length(); i++){path = word.charAt(i)-'a';if(tree[cur][path]==0){return0;}cur = tree[cur][path];}return end[cur];}publicstaticintprefixNumber(String pre){int cur =1;for(int i =0, path; i < pre.length(); i++){path = pre.charAt(i)-'a';if(tree[cur][path]==0){return0;}cur = tree[cur][path];}return pass[cur];}publicstaticvoiddelete(String word){if(search(word)>0){int cur =1;for(int i =0, path; i < word.length(); i++){path = word.charAt(i)-'a';if(--pass[tree[cur][path]]==0){tree[cur][path]=0;return;}cur = tree[cur][path];}end[cur]--;}}publicstaticvoidclear(){for(int i =1; i <= cnt; i++){Arrays.fill(tree[i],0);end[i]=0;pass[i]=0;}}publicstaticint m, op;publicstaticString[] splits;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader in =newBufferedReader(newInputStreamReader(System.in));PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));String line =null;while((line = in.readLine())!=null){build();m =Integer.valueOf(line);for(int i =1; i <= m; i++){splits = in.readLine().split(" ");op =Integer.valueOf(splits[0]);if(op ==1){insert(splits[1]);}elseif(op ==2){delete(splits[1]);}elseif(op ==3){out.println(search(splits[1])>0?"YES":"NO");}elseif(op ==4){out.println(prefixNumber(splits[1]));}}clear();}out.flush();in.close();out.close();}}
45. (必备)前缀树的相关题目
packageclass045;importjava.util.Arrays;// 牛牛和他的朋友们约定了一套接头密匙系统,用于确认彼此身份// 密匙由一组数字序列表示,两个密匙被认为是一致的,如果满足以下条件:// 密匙 b 的长度不超过密匙 a 的长度。// 对于任意 0 <= i < length(b),有b[i+1] - b[i] == a[i+1] - a[i]// 现在给定了m个密匙 b 的数组,以及n个密匙 a 的数组// 请你返回一个长度为 m 的结果数组 ans,表示每个密匙b都有多少一致的密匙// 数组 a 和数组 b 中的元素个数均不超过 10^5// 1 <= m, n <= 1000// 测试链接 : https://www.nowcoder.com/practice/c552d3b4dfda49ccb883a6371d9a6932publicclassCode01_CountConsistentKeys{publicstaticint[]countConsistentKeys(int[][] b,int[][] a){build();StringBuilder builder =newStringBuilder();// [3,6,50,10] -> "3#44#-40#"for(int[] nums : a){builder.setLength(0);for(int i =1; i < nums.length; i++){builder.append(String.valueOf(nums[i]- nums[i -1])+"#");}insert(builder.toString());}int[] ans =newint[b.length];for(int i =0; i < b.length; i++){builder.setLength(0);int[] nums = b[i];for(int j =1; j < nums.length; j++){builder.append(String.valueOf(nums[j]- nums[j -1])+"#");}ans[i]=count(builder.toString());}clear();return ans;}// 如果将来增加了数据量,就改大这个值publicstaticintMAXN=2000001;publicstaticint[][] tree =newint[MAXN][12];publicstaticint[] pass =newint[MAXN];publicstaticint cnt;publicstaticvoidbuild(){cnt =1;}// '0' ~ '9' 10个 0~9// '#' 10// '-' 11publicstaticintpath(char cha){if(cha =='#'){return10;}elseif(cha =='-'){return11;}else{return cha -'0';}}publicstaticvoidinsert(String word){int cur =1;pass[cur]++;for(int i =0, path; i < word.length(); i++){path =path(word.charAt(i));if(tree[cur][path]==0){tree[cur][path]=++cnt;}cur = tree[cur][path];pass[cur]++;}}publicstaticintcount(String pre){int cur =1;for(int i =0, path; i < pre.length(); i++){path =path(pre.charAt(i));if(tree[cur][path]==0){return0;}cur = tree[cur][path];}return pass[cur];}publicstaticvoidclear(){for(int i =1; i <= cnt; i++){Arrays.fill(tree[i],0);pass[i]=0;}}}
packageclass045;importjava.util.HashSet;// 数组中两个数的最大异或值// 给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0<=i<=j<=n// 1 <= nums.length <= 2 * 10^5// 0 <= nums[i] <= 2^31 - 1// 测试链接 : https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/publicclassCode02_TwoNumbersMaximumXor{// 前缀树的做法// 好想publicstaticintfindMaximumXOR1(int[] nums){build(nums);int ans =0;for(int num : nums){ans =Math.max(ans,maxXor(num));}clear();return ans;}// 准备这么多静态空间就够了,实验出来的// 如果测试数据升级了规模,就改大这个值publicstaticintMAXN=3000001;publicstaticint[][] tree =newint[MAXN][2];// 前缀树目前使用了多少空间publicstaticint cnt;// 数字只需要从哪一位开始考虑publicstaticint high;publicstaticvoidbuild(int[] nums){cnt =1;// 找个最大值int max =Integer.MIN_VALUE;for(int num : nums){max =Math.max(num, max);}// 计算数组最大值的二进制状态,有多少个前缀的0// 可以忽略这些前置的0,从left位开始考虑high =31-Integer.numberOfLeadingZeros(max);for(int num : nums){insert(num);}}publicstaticvoidinsert(int num){int cur =1;for(int i = high, path; i >=0; i--){path =(num >> i)&1;if(tree[cur][path]==0){tree[cur][path]=++cnt;}cur = tree[cur][path];}}publicstaticintmaxXor(int num){// 最终异或的结果(尽量大)int ans =0;// 前缀树目前来到的节点编号int cur =1;for(int i = high, status, want; i >=0; i--){// status : num第i位的状态status =(num >> i)&1;// want : num第i位希望遇到的状态want = status ^1;if(tree[cur][want]==0){// 询问前缀树,能不能达成// 不能达成want ^=1;}// want变成真的往下走的路ans |=(status ^ want)<< i;cur = tree[cur][want];}return ans;}publicstaticvoidclear(){for(int i =1; i <= cnt; i++){tree[i][0]= tree[i][1]=0;}}// 用哈希表的做法// 难想publicintfindMaximumXOR2(int[] nums){int max =Integer.MIN_VALUE;for(int num : nums){max =Math.max(num, max);}int ans =0;HashSet<Integer> set =newHashSet<>();for(int i =31-Integer.numberOfLeadingZeros(max); i >=0; i--){// ans : 31....i+1 已经达成的目标int better = ans |(1<< i);set.clear();for(int num : nums){// num : 31.....i 这些状态保留,剩下全成0num =(num >> i)<< i;set.add(num);// num ^ 某状态 是否能 达成better目标,就在set中找 某状态 : better ^ numif(set.contains(better ^ num)){ans = better;break;}}}return ans;}}
packageclass045;importjava.util.ArrayList;importjava.util.Arrays;importjava.util.List;// 在二维字符数组中搜索可能的单词// 给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words// 返回所有二维网格上的单词。单词必须按照字母顺序,通过 相邻的单元格 内的字母构成// 其中“相邻”单元格是那些水平相邻或垂直相邻的单元格// 同一个单元格内的字母在一个单词中不允许被重复使用// 1 <= m, n <= 12// 1 <= words.length <= 3 * 10^4// 1 <= words[i].length <= 10// 测试链接 : https://leetcode.cn/problems/word-search-ii/publicclassCode03_WordSearchII{publicstaticList<String>findWords(char[][] board,String[] words){build(words);List<String> ans =newArrayList<>();for(int i =0; i < board.length; i++){for(int j =0; j < board[0].length; j++){dfs(board, i, j,1, ans);}}clear();return ans;}// board : 二维网格// i,j : 此时来到的格子位置,i行、j列// t : 前缀树的编号// List<String> ans : 收集到了哪些字符串,都放入ans// 返回值 : 收集到了几个字符串publicstaticintdfs(char[][] board,int i,int j,int t,List<String> ans){// 越界 或者 走了回头路,直接返回0if(i <0|| i == board.length || j <0|| j == board[0].length || board[i][j]==0){return0;}// 不越界 且 不是回头路// 用tmp记录当前字符char tmp = board[i][j];// 路的编号// a -> 0// b -> 1// ...// z -> 25int road = tmp -'a';t = tree[t][road];if(pass[t]==0){return0;}// i,j位置有必要来// fix :从当前i,j位置出发,一共收集到了几个字符串int fix =0;if(end[t]!=null){fix++;ans.add(end[t]);end[t]=null;}// 把i,j位置的字符,改成0,后续的过程,是不可以再来到i,j位置的!board[i][j]=0;fix +=dfs(board, i -1, j, t, ans);fix +=dfs(board, i +1, j, t, ans);fix +=dfs(board, i, j -1, t, ans);fix +=dfs(board, i, j +1, t, ans);pass[t]-= fix;board[i][j]= tmp;return fix;}publicstaticintMAXN=10001;publicstaticint[][] tree =newint[MAXN][26];publicstaticint[] pass =newint[MAXN];publicstaticString[] end =newString[MAXN];publicstaticint cnt;publicstaticvoidbuild(String[] words){cnt =1;for(String word : words){int cur =1;pass[cur]++;for(int i =0, path; i < word.length(); i++){path = word.charAt(i)-'a';if(tree[cur][path]==0){tree[cur][path]=++cnt;}cur = tree[cur][path];pass[cur]++;}end[cur]= word;}}publicstaticvoidclear(){for(int i =1; i <= cnt; i++){Arrays.fill(tree[i],0);pass[i]=0;end[i]=null;}}}