packageclass047;// 航班预订统计// 这里有 n 个航班,它们分别从 1 到 n 进行编号。// 有一份航班预订表 bookings ,// 表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi]// 意味着在从 firsti 到 lasti //(包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。// 请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。// 测试链接 : https://leetcode.cn/problems/corporate-flight-bookings/publicclassCode01_CorporateFlightBookings{// bookings// [1,5,6]// [2,9,3]// ...publicstaticint[]corpFlightBookings(int[][] bookings,int n){int[] cnt =newint[n +2];// 设置差分数组,每一个操作对应两个设置for(int[] book : bookings){
cnt[book[0]]+= book[2];
cnt[book[1]+1]-= book[2];}// 加工前缀和for(int i =1; i < cnt.length; i++){
cnt[i]+= cnt[i -1];}int[] ans =newint[n];for(int i =0; i < n; i++){
ans[i]= cnt[i +1];}return ans;}}
packageclass047;// 一开始1~n范围上的数字都是0,一共有m个操作,每次操作为(l,r,s,e,d)// 表示在l~r范围上依次加上首项为s、末项为e、公差为d的数列// m个操作做完之后,统计1~n范围上所有数字的最大值和异或和// 测试链接 : https://www.luogu.com.cn/problem/P4231// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode02_ArithmeticSequenceDifference{publicstaticintMAXN=10000005;publicstaticlong[] arr =newlong[MAXN];publicstaticint n, m;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;
in.nextToken();
m =(int) in.nval;for(int i =0, l, r, s, e; i < m; i++){
in.nextToken(); l =(int) in.nval;
in.nextToken(); r =(int) in.nval;
in.nextToken(); s =(int) in.nval;
in.nextToken(); e =(int) in.nval;set(l, r, s, e,(e - s)/(r - l));}build();long max =0, xor =0;for(int i =1; i <= n; i++){
max =Math.max(max, arr[i]);
xor ^= arr[i];}
out.println(xor +" "+ max);}
out.flush();
out.close();
br.close();}publicstaticvoidset(int l,int r,int s,int e,int d){
arr[l]+= s;
arr[l +1]+= d - s;
arr[r +1]-= d + e;
arr[r +2]+= e;}publicstaticvoidbuild(){for(int i =1; i <= n; i++){
arr[i]+= arr[i -1];}for(int i =1; i <= n; i++){
arr[i]+= arr[i -1];}}}
packageclass047;// 一群人落水后求每个位置的水位高度// 问题描述比较复杂,见测试链接// 测试链接 : https://www.luogu.com.cn/problem/P5026// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode03_WaterHeight{// 题目说了m <= 10^6,代表湖泊宽度// 这就是MAXN的含义,湖泊最大宽度的极限publicstaticintMAXN=1000001;// 数值保护,因为题目中v最大是10000// 所以左侧影响最远的位置到达了x - 3 * v + 1// 所以右侧影响最远的位置到达了x + 3 * v - 1// x如果是正式的位置(1~m),那么左、右侧可能超过正式位置差不多30000的规模// 这就是OFFSET的含义publicstaticintOFFSET=30001;// 湖泊宽度是MAXN,是正式位置的范围// 左、右侧可能超过正式位置差不多OFFSET的规模// 所以准备一个长度为OFFSET + MAXN + OFFSET的数组// 这样一来,左侧影响最远的位置...右侧影响最远的位置,// 都可以被arr中的下标表示出来,就省去了很多越界讨论// 详细解释看set方法的注释publicstaticint[] arr =newint[OFFSET+MAXN+OFFSET];publicstaticint n, m;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){// n有多少个人落水,每个人落水意味着四个等差数列操作
n =(int) in.nval;
in.nextToken();// 一共有多少位置,1~m个位置,最终要打印每个位置的水位
m =(int) in.nval;for(int i =0, v, x; i < n; i++){
in.nextToken(); v =(int) in.nval;
in.nextToken(); x =(int) in.nval;// v体积的朋友,在x处落水,修改差分数组fall(v, x);}// 生成最终的水位数组build();// 开始收集答案// 0...OFFSET这些位置是辅助位置,为了防止越界设计的// 从OFFSET+1开始往下数m个,才是正式的位置1...m// 打印这些位置,就是返回正式位置1...m的水位int start =OFFSET+1;
out.print(arr[start++]);for(int i =2; i <= m; i++){
out.print(" "+ arr[start++]);}
out.println();}
out.flush();
out.close();
br.close();}publicstaticvoidfall(int v,int x){set(x -3* v +1, x -2* v,1, v,1);set(x -2* v +1, x, v -1,-v,-1);set(x +1, x +2* v,-v +1, v,1);set(x +2* v +1, x +3* v -1, v -1,1,-1);}publicstaticvoidset(int l,int r,int s,int e,int d){// 为了防止x - 3 * v + 1出现负数下标,进而有很多很烦的边界讨论// 所以任何位置,都加上一个较大的数字(OFFSET)// 这样一来,所有下标就都在0以上了,省去了大量边界讨论// 这就是为什么arr在初始化的时候要准备OFFSET + MAXN + OFFSET这么多的空间
arr[l +OFFSET]+= s;
arr[l +1+OFFSET]+= d - s;
arr[r +1+OFFSET]-= d + e;
arr[r +2+OFFSET]+= e;}publicstaticvoidbuild(){for(int i =1; i <= m +OFFSET; i++){
arr[i]+= arr[i -1];}for(int i =1; i <= m +OFFSET; i++){
arr[i]+= arr[i -1];}}}
48. (必备)二维前缀和, 二维差分, 离散化技巧
packageclass048;// 利用二维前缀和信息迅速得到二维区域和// 测试链接 : https://leetcode.cn/problems/range-sum-query-2d-immutable/publicclassCode01_PrefixSumMatrix{classNumMatrix{publicint[][] sum;publicNumMatrix(int[][] matrix){int n = matrix.length;int m = matrix[0].length;
sum =newint[n +1][m +1];for(int a =1, c =0; c < n; a++, c++){for(int b =1, d =0; d < m; b++, d++){
sum[a][b]= matrix[c][d];}}for(int i =1; i <= n; i++){for(int j =1; j <= m; j++){
sum[i][j]+= sum[i][j -1]+ sum[i -1][j]- sum[i -1][j -1];}}}publicintsumRegion(int a,int b,int c,int d){
c++;
d++;return sum[c][d]- sum[c][b]- sum[a][d]+ sum[a][b];}}}
packageclass048;// 边框为1的最大正方形// 给你一个由若干 0 和 1 组成的二维网格 grid// 请你找出边界全部由 1 组成的最大 正方形 子网格// 并返回该子网格中的元素数量。如果不存在,则返回 0。// 测试链接 : https://leetcode.cn/problems/largest-1-bordered-square/publicclassCode02_LargestOneBorderedSquare{// 打败比例不高,但完全是常数时间的问题// 时间复杂度O(n * m * min(n,m)),额外空间复杂度O(1)// 复杂度指标上绝对是最优解publicstaticintlargest1BorderedSquare(int[][] g){int n = g.length;int m = g[0].length;build(n, m, g);if(sum(g,0,0, n -1, m -1)==0){return0;}// 找到的最大合法正方形的边长int ans =1;for(int a =0; a < n; a++){for(int b =0; b < m; b++){// (a,b)所有左上角点// (c,d)更大边长的右下角点,k是当前尝试的边长for(int c = a + ans, d = b + ans, k = ans +1; c < n && d < m; c++, d++, k++){if(sum(g, a, b, c, d)-sum(g, a +1, b +1, c -1, d -1)==(k -1)<<2){
ans = k;}}}}return ans * ans;}// g : 原始二维数组// 把g变成原始二维数组的前缀和数组sum,复用自己// 不能补0行,0列,都是0publicstaticvoidbuild(int n,int m,int[][] g){for(int i =0; i < n; i++){for(int j =0; j < m; j++){
g[i][j]+=get(g, i, j -1)+get(g, i -1, j)-get(g, i -1, j -1);}}}publicstaticintsum(int[][] g,int a,int b,int c,int d){return a > c ?0:(g[c][d]-get(g, c, b -1)-get(g, a -1, d)+get(g, a -1, b -1));}publicstaticintget(int[][] g,int i,int j){return(i <0|| j <0)?0: g[i][j];}}
packageclass048;// 二维差分模版(洛谷)// 测试链接 : https://www.luogu.com.cn/problem/P3397// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode03_DiffMatrixLuogu{publicstaticintMAXN=1002;publicstaticint[][] diff =newint[MAXN][MAXN];publicstaticint n, q;publicstaticvoidadd(int a,int b,int c,int d,int k){
diff[a][b]+= k;
diff[c +1][b]-= k;
diff[a][d +1]-= k;
diff[c +1][d +1]+= k;}publicstaticvoidbuild(){for(int i =1; i <= n; i++){for(int j =1; j <= n; j++){
diff[i][j]+= diff[i -1][j]+ diff[i][j -1]- diff[i -1][j -1];}}}publicstaticvoidclear(){for(int i =1; i <= n +1; i++){for(int j =1; j <= n +1; j++){
diff[i][j]=0;}}}publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;
in.nextToken();
q =(int) in.nval;for(int i =1, a, b, c, d; i <= q; i++){
in.nextToken();
a =(int) in.nval;
in.nextToken();
b =(int) in.nval;
in.nextToken();
c =(int) in.nval;
in.nextToken();
d =(int) in.nval;add(a, b, c, d,1);}build();for(int i =1; i <= n; i++){
out.print(diff[i][1]);for(int j =2; j <= n; j++){
out.print(" "+ diff[i][j]);}
out.println();}clear();}
out.flush();
out.close();
br.close();}}
packageclass048;// 二维差分模版(牛客)// 测试链接 : https://www.nowcoder.com/practice/50e1a93989df42efb0b1dec386fb4ccc// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode03_DiffMatrixNowcoder{publicstaticintMAXN=1005;publicstaticintMAXM=1005;publicstaticlong[][] diff =newlong[MAXN][MAXM];publicstaticint n, m, q;publicstaticvoidadd(int a,int b,int c,int d,int k){
diff[a][b]+= k;
diff[c +1][b]-= k;
diff[a][d +1]-= k;
diff[c +1][d +1]+= k;}publicstaticvoidbuild(){for(int i =1; i <= n; i++){for(int j =1; j <= m; j++){
diff[i][j]+= diff[i -1][j]+ diff[i][j -1]- diff[i -1][j -1];}}}publicstaticvoidclear(){for(int i =1; i <= n +1; i++){for(int j =1; j <= m +1; j++){
diff[i][j]=0;}}}publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;
in.nextToken();
m =(int) in.nval;
in.nextToken();
q =(int) in.nval;for(int i =1; i <= n; i++){for(int j =1; j <= m; j++){
in.nextToken();add(i, j, i, j,(int) in.nval);}}for(int i =1, a, b, c, d, k; i <= q; i++){
in.nextToken();
a =(int) in.nval;
in.nextToken();
b =(int) in.nval;
in.nextToken();
c =(int) in.nval;
in.nextToken();
d =(int) in.nval;
in.nextToken();
k =(int) in.nval;add(a, b, c, d, k);}build();for(int i =1; i <= n; i++){
out.print(diff[i][1]);for(int j =2; j <= m; j++){
out.print(" "+ diff[i][j]);}
out.println();}clear();}
out.flush();
out.close();
br.close();}}
packageclass048;// 用邮票贴满网格图// 给你一个 m * n 的二进制矩阵 grid// 每个格子要么为 0 (空)要么为 1 (被占据)// 给你邮票的尺寸为 stampHeight * stampWidth// 我们想将邮票贴进二进制矩阵中,且满足以下 限制 和 要求 :// 覆盖所有空格子,不覆盖任何被占据的格子// 可以放入任意数目的邮票,邮票可以相互有重叠部分// 邮票不允许旋转,邮票必须完全在矩阵内// 如果在满足上述要求的前提下,可以放入邮票,请返回 true ,否则返回 false// 测试链接 : https://leetcode.cn/problems/stamping-the-grid/publicclassCode04_StampingTheGrid{// 时间复杂度O(n*m),额外空间复杂度O(n*m)publicstaticbooleanpossibleToStamp(int[][] grid,int h,int w){int n = grid.length;int m = grid[0].length;// sum是前缀和数组// 查询原始矩阵中的某个范围的累加和很快速int[][] sum =newint[n +1][m +1];for(int i =0; i < n; i++){for(int j =0; j < m; j++){
sum[i +1][j +1]= grid[i][j];}}build(sum);// 差分矩阵// 当贴邮票的时候,不再原始矩阵里贴,在差分矩阵里贴// 原始矩阵就用来判断能不能贴邮票,不进行修改// 每贴一张邮票都在差分矩阵里修改int[][] diff =newint[n +2][m +2];for(int a =1, c = a + h -1; c <= n; a++, c++){for(int b =1, d = b + w -1; d <= m; b++, d++){// 原始矩阵中 (a,b)左上角点// 根据邮票规格,h、w,算出右下角点(c,d)// 这个区域彻底都是0,那么: // sumRegion(sum, a, b, c, d) == 0// 那么此时这个区域可以贴邮票if(sumRegion(sum, a, b, c, d)==0){add(diff, a, b, c, d);}}}build(diff);// 检查所有的格子!for(int i =0; i < n; i++){for(int j =0; j < m; j++){// 原始矩阵里:grid[i][j] == 0,说明是个洞// 差分矩阵里:diff[i + 1][j + 1] == 0,说明洞上并没有邮票// 此时返回falseif(grid[i][j]==0&& diff[i +1][j +1]==0){returnfalse;}}}returntrue;}publicstaticvoidbuild(int[][] m){for(int i =1; i < m.length; i++){for(int j =1; j < m[0].length; j++){
m[i][j]+= m[i -1][j]+ m[i][j -1]- m[i -1][j -1];}}}publicstaticintsumRegion(int[][] sum,int a,int b,int c,int d){return sum[c][d]- sum[c][b -1]- sum[a -1][d]+ sum[a -1][b -1];}publicstaticvoidadd(int[][] diff,int a,int b,int c,int d){
diff[a][b]+=1;
diff[c +1][d +1]+=1;
diff[c +1][b]-=1;
diff[a][d +1]-=1;}}
packageclass048;importjava.util.Arrays;// 最强祝福力场// 小扣在探索丛林的过程中,无意间发现了传说中"落寞的黄金之都"// 而在这片建筑废墟的地带中,小扣使用探测仪监测到了存在某种带有「祝福」效果的力场// 经过不断的勘测记录,小扣将所有力场的分布都记录了下来// forceField[i] = [x,y,side] // 表示第 i 片力场将覆盖以坐标 (x,y) 为中心,边长为 side 的正方形区域。// 若任意一点的 力场强度 等于覆盖该点的力场数量// 请求出在这片地带中 力场强度 最强处的 力场强度// 注意:力场范围的边缘同样被力场覆盖。// 测试链接 : https://leetcode.cn/problems/xepqZ5/publicclassCode05_StrongestForceField{// 时间复杂度O(n^2),额外空间复杂度O(n^2),n是力场的个数publicstaticintfieldOfGreatestBlessing(int[][] fields){int n = fields.length;// n : 矩形的个数,x 2*n个坐标long[] xs =newlong[n <<1];long[] ys =newlong[n <<1];for(int i =0, k =0, p =0; i < n; i++){long x = fields[i][0];long y = fields[i][1];long r = fields[i][2];
xs[k++]=(x <<1)- r;
xs[k++]=(x <<1)+ r;
ys[p++]=(y <<1)- r;
ys[p++]=(y <<1)+ r;}// xs数组中,排序了且相同值只留一份,返回有效长度int sizex =sort(xs);// ys数组中,排序了且相同值只留一份,返回有效长度int sizey =sort(ys);// n个力场,sizex : 2 * n, sizey : 2 * nint[][] diff =newint[sizex +2][sizey +2];for(int i =0, a, b, c, d; i < n; i++){long x = fields[i][0];long y = fields[i][1];long r = fields[i][2];
a =rank(xs,(x <<1)- r, sizex);
b =rank(ys,(y <<1)- r, sizey);
c =rank(xs,(x <<1)+ r, sizex);
d =rank(ys,(y <<1)+ r, sizey);add(diff, a, b, c, d);}int ans =0;// O(n^2)for(int i =1; i < diff.length; i++){for(int j =1; j < diff[0].length; j++){
diff[i][j]+= diff[i -1][j]+ diff[i][j -1]- diff[i -1][j -1];
ans =Math.max(ans, diff[i][j]);}}return ans;}// [50,70,30,70,30,60] 长度6// [30,30,50,60,70,70]// [30,50,60,70] 60 -> 3// 1 2 3 4// 长度4,publicstaticintsort(long[] nums){Arrays.sort(nums);int size =1;for(int i =1; i < nums.length; i++){if(nums[i]!= nums[size -1]){
nums[size++]= nums[i];}}return size;}// nums 有序数组,有效长度是size,0~size-1范围上无重复值// 已知v一定在nums[0~size-1],返回v所对应的编号publicstaticintrank(long[] nums,long v,int size){int l =0;int r = size -1;int m, ans =0;while(l <= r){
m =(l + r)/2;if(nums[m]>= v){
ans = m;
r = m -1;}else{
l = m +1;}}return ans +1;}// 二维差分publicstaticvoidadd(int[][] diff,int a,int b,int c,int d){
diff[a][b]+=1;
diff[c +1][d +1]+=1;
diff[c +1][b]-=1;
diff[a][d +1]-=1;}}
49. (必备)滑动窗口技巧以及相关题目
packageclass049;// 累加和大于等于target的最短子数组长度// 给定一个含有 n 个正整数的数组和一个正整数 target// 找到累加和 >= target 的长度最小的子数组并返回其长度// 如果不存在符合条件的子数组返回0// 测试链接 : https://leetcode.cn/problems/minimum-size-subarray-sum/publicclassCode01_MinimumSizeSubarraySum{publicstaticintminSubArrayLen(int target,int[] nums){int ans =Integer.MAX_VALUE;for(int l =0, r =0, sum =0; r < nums.length; r++){
sum += nums[r];while(sum - nums[l]>= target){// sum : nums[l....r]// 如果l位置的数从窗口出去,还能继续达标,那就出去
sum -= nums[l++];}if(sum >= target){
ans =Math.min(ans, r - l +1);}}return ans ==Integer.MAX_VALUE?0: ans;}}
packageclass049;importjava.util.Arrays;// 无重复字符的最长子串// 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。// 测试链接 : https://leetcode.cn/problems/longest-substring-without-repeating-characters/publicclassCode02_LongestSubstringWithoutRepeatingCharacters{publicstaticintlengthOfLongestSubstring(String str){char[] s = str.toCharArray();int n = s.length;// char -> int -> 0 ~ 255// 每一种字符上次出现的位置int[] last =newint[256];// 所有字符都没有上次出现的位置Arrays.fill(last,-1);// 不含有重复字符的 最长子串 的长度int ans =0;for(int l =0, r =0; r < n; r++){
l =Math.max(l, last[s[r]]+1);
ans =Math.max(ans, r - l +1);// 更新当前字符上一次出现的位置
last[s[r]]= r;}return ans;}}
packageclass049;// 最小覆盖子串// 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串// 如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。// 测试链接 : https://leetcode.cn/problems/minimum-window-substring/publicclassCode03_MinimumWindowSubstring{publicstaticStringminWindow(String str,String tar){if(str.length()< tar.length()){return"";}char[] s = str.toCharArray();char[] t = tar.toCharArray();int[] cnts =newint[256];for(char cha : t){
cnts[cha]--;}// 最小覆盖子串的长度int len =Integer.MAX_VALUE;// 从哪个位置开头,发现的这个最小覆盖子串int start =0;for(int l =0, r =0, debt = t.length; r < s.length; r++){// s[r] 当前字符 -> int// cnts[s[r]] : 当前字符欠债情况,负数就是欠债,正数就是多给的if(cnts[s[r]]++<0){
debt--;}if(debt ==0){// r位置结尾,真的有覆盖子串!// 看看这个覆盖子串能不能尽量短while(cnts[s[l]]>0){// l位置的字符能拿回
cnts[s[l++]]--;}// 从while里面出来,// l....r就是r位置结尾的最小覆盖子串if(r - l +1< len){
len = r - l +1;
start = l;}}}return len ==Integer.MAX_VALUE?"": str.substring(start, start + len);}}
packageclass049;// 加油站// 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。// 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升// 你从其中的一个加油站出发,开始时油箱为空。// 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周// 则返回出发时加油站的编号,否则返回 -1// 如果存在解,则 保证 它是 唯一 的。// 测试链接 : https://leetcode.cn/problems/gas-station/publicclassCode04_GasStation{publicstaticintcanCompleteCircuit(int[] gas,int[] cost){int n = gas.length;// 车辆尝试从0~n-1出发,看能不能走一圈,l// r : 窗口即将进来数字的位置// len : 窗口大小// sum : 窗口累加和for(int l =0, r =0, len =0, sum =0; l < n; l++){while(sum >=0){// 当前窗口累加和>=0,尝试扩if(len == n){return l;}// r : 窗口即将进来数字的位置
r =(l +(len++))% n;
sum += gas[r]- cost[r];}// sum < 0,此时l位置无法转一圈
len--;
sum -= gas[l]- cost[l];}return-1;}}
packageclass049;// 替换子串得到平衡字符串// 有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。// 假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。// 给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。// 你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。// 请返回待替换子串的最小可能长度。// 如果原字符串自身就是一个平衡字符串,则返回 0。// 测试链接 : https://leetcode.cn/problems/replace-the-substring-for-balanced-string/publicclassCode05_ReplaceTheSubstringForBalancedString{// Q W E R// 0 1 2 3// "W Q Q R R E"// 1 0 0 3 3 2// cnts[1] = 1;// cnts[0] = 2;// cnts[2] = 1;// cnts[3] = 2;publicstaticintbalancedString(String str){int n = str.length();int[] arr =newint[n];int[] cnts =newint[4];for(int i =0; i < n; i++){char c = str.charAt(i);
arr[i]= c =='W'?1:(c =='E'?2:(c =='R'?3:0));
cnts[arr[i]]++;}// str : 长度是4的整数倍,n// 每种字符出现的个数 : n/4int require = n /4;// 至少要修改多长的子串,才能做到四种字符一样多int ans = n;// 自由变化的窗口l....rfor(int l =0, r =0; l < n; l++){// l = 0, r= 0, 窗口0长度// l...r-1 : [l,r)while(!ok(cnts, r - l, require)&& r < n){// cnts : 窗口之外的统计
cnts[arr[r++]]--;}// 1) l...r-1 [l,r) ,做到了!// 2) r == n,也没做到if(ok(cnts, r - l, require)){
ans =Math.min(ans, r - l);}// [l,r),不被cnts统计到的// l+1
cnts[arr[l]]++;}return ans;}// cnts : l...r范围上的字符不算!在自由变化的窗口之外,每一种字符的词频统计// len : 自由变化窗口的长度// require : 每一种字符都要达到的数量// 返回值 : 请问能不能做到publicstaticbooleanok(int[] cnts,int len,int require){for(int i =0; i <4; i++){// 0 1 2 3if(cnts[i]> require){returnfalse;}// require - cnts[i] : 20 - 16 = 4
len -= require - cnts[i];}return len ==0;}}
packageclass049;importjava.util.Arrays;// K个不同整数的子数组// 给定一个正整数数组 nums和一个整数 k,返回 nums 中 「好子数组」 的数目。// 如果 nums 的某个子数组中不同整数的个数恰好为 k// 则称 nums 的这个连续、不一定不同的子数组为 「好子数组 」。// 例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。// 子数组 是数组的 连续 部分。// 测试链接 : https://leetcode.cn/problems/subarrays-with-k-different-integers/publicclassCode06_SubarraysWithKDifferentIntegers{publicstaticintsubarraysWithKDistinct(int[] arr,int k){returnnumsOfMostKinds(arr, k)-numsOfMostKinds(arr, k -1);}publicstaticintMAXN=20001;publicstaticint[] cnts =newint[MAXN];// arr中有多少子数组,数字种类不超过k// arr的长度是n,arr里的数值1~n之间publicstaticintnumsOfMostKinds(int[] arr,int k){Arrays.fill(cnts,1, arr.length +1,0);int ans =0;for(int l =0, r =0, collect =0; r < arr.length; r++){// r(刚进)if(++cnts[arr[r]]==1){
collect++;}// l.....r 要求不超过3种,已经4种,l往右(吐数字)while(collect > k){if(--cnts[arr[l++]]==0){
collect--;}}// l.....r不超过了// 0...3// 0~3// 1~3// 2~3// 3~3
ans += r - l +1;}return ans;}}
packageclass049;importjava.util.Arrays;// 至少有K个重复字符的最长子串// 给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串// 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度// 如果不存在这样的子字符串,则返回 0。// 测试链接 : https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/publicclassCode07_LongestSubstringWithAtLeastKRepeating{publicstaticintlongestSubstring(String str,int k){char[] s = str.toCharArray();int n = s.length;int[] cnts =newint[256];int ans =0;// 每次要求子串必须含有require种字符,每种字符都必须>=k次,这样的最长子串是多长for(int require =1; require <=26; require++){Arrays.fill(cnts,0);// collect : 窗口中一共收集到的种类数// satisfy : 窗口中达标的种类数(次数>=k)for(int l =0, r =0, collect =0, satisfy =0; r < n; r++){
cnts[s[r]]++;if(cnts[s[r]]==1){
collect++;}if(cnts[s[r]]== k){
satisfy++;}// l....r 种类超了!// l位置的字符,窗口中吐出来!while(collect > require){if(cnts[s[l]]==1){
collect--;}if(cnts[s[l]]== k){
satisfy--;}
cnts[s[l++]]--;}// l.....r : 子串以r位置的字符结尾,且种类数不超的,最大长度!if(satisfy == require){
ans =Math.max(ans, r - l +1);}}}return ans;}}
50. (必备)双指针技巧及相关题目
packageclass050;// 按奇偶排序数组II// 给定一个非负整数数组 nums。nums 中一半整数是奇数 ,一半整数是偶数// 对数组进行排序,以便当 nums[i] 为奇数时,i也是奇数// 当 nums[i] 为偶数时, i 也是 偶数// 你可以返回 任何满足上述条件的数组作为答案// 测试链接 : https://leetcode.cn/problems/sort-array-by-parity-ii/publicclassCode01_SortArrayByParityII{// 时间复杂度O(n),额外空间复杂度O(1)publicstaticint[]sortArrayByParityII(int[] nums){int n = nums.length;for(int odd =1, even =0; odd < n && even < n;){if((nums[n -1]&1)==1){swap(nums, odd, n -1);
odd +=2;}else{swap(nums, even, n -1);
even +=2;}}return nums;}publicstaticvoidswap(int[] nums,int i,int j){int tmp = nums[i];
nums[i]= nums[j];
nums[j]= tmp;}}
packageclass050;// 缺失的第一个正数// 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。// 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。// 测试链接 : https://leetcode.cn/problems/first-missing-positive/publicclassCode07_FirstMissingPositive{// 时间复杂度O(n),额外空间复杂度O(1)publicstaticintfirstMissingPositive(int[] arr){// l的左边,都是做到i位置上放着i+1的区域// 永远盯着l位置的数字看,看能不能扩充(l++)int l =0;// [r....]垃圾区// 最好的状况下,认为1~r是可以收集全的,每个数字收集1个,不能有垃圾// 有垃圾呢?预期就会变差(r--)int r = arr.length;while(l < r){if(arr[l]== l +1){
l++;}elseif(arr[l]<= l || arr[l]> r || arr[arr[l]-1]== arr[l]){swap(arr, l,--r);}else{swap(arr, l, arr[l]-1);}}return l +1;}publicstaticvoidswap(int[] arr,int i,int j){int tmp = arr[i];
arr[i]= arr[j];
arr[j]= tmp;}}
51. (必备)二分答案法及相关题目
packageclass051;// 爱吃香蕉的珂珂// 珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉// 警卫已经离开了,将在 h 小时后回来。// 珂珂可以决定她吃香蕉的速度 k (单位:根/小时)// 每个小时,她将会选择一堆香蕉,从中吃掉 k 根// 如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉// 珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。// 返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)// 测试链接 : https://leetcode.cn/problems/koko-eating-bananas/publicclassCode01_KokoEatingBananas{// 时间复杂度O(n * log(max)),额外空间复杂度O(1)publicstaticintminEatingSpeed(int[] piles,int h){// 最小且达标的速度,范围[l,r]int l =1;int r =0;for(int pile : piles){
r =Math.max(r, pile);}// [l,r]不停二分int ans =0;int m =0;while(l <= r){// m = (l + r) / 2
m = l +((r - l)>>1);if(f(piles, m)<= h){// 达标!// 记录答案,去左侧二分
ans = m;// l....m....r// l..m-1
r = m -1;}else{// 不达标
l = m +1;}}return ans;}// 香蕉重量都在piles// 速度就定成speed// 返回吃完所有的香蕉,耗费的小时数量publicstaticlongf(int[] piles,int speed){long ans =0;for(int pile : piles){// (a/b)结果向上取整,如果a和b都是非负数,可以写成(a+b-1)/b// "讲解032-位图"讲了这种写法,不会的同学可以去看看// 这里不再赘述
ans +=(pile + speed -1)/ speed;}return ans;}}
packageclass051;// 分割数组的最大值(画匠问题)// 给定一个非负整数数组 nums 和一个整数 m// 你需要将这个数组分成 m 个非空的连续子数组。// 设计一个算法使得这 m 个子数组各自和的最大值最小。// 测试链接 : https://leetcode.cn/problems/split-array-largest-sum/publicclassCode02_SplitArrayLargestSum{// 时间复杂度O(n * log(sum)),额外空间复杂度O(1)publicstaticintsplitArray(int[] nums,int k){long sum =0;for(int num : nums){
sum += num;}long ans =0;// [0,sum]二分for(long l =0, r = sum, m, need; l <= r;){// 中点m
m = l +((r - l)>>1);// 必须让数组每一部分的累加和 <= m,请问划分成几个部分才够!
need =f(nums, m);if(need <= k){// 达标
ans = m;
r = m -1;}else{
l = m +1;}}return(int) ans;}// 必须让数组arr每一部分的累加和 <= limit,请问划分成几个部分才够!// 返回需要的部分数量publicstaticintf(int[] arr,long limit){int parts =1;int sum =0;for(int num : arr){if(num > limit){returnInteger.MAX_VALUE;}if(sum + num > limit){
parts++;
sum = num;}else{
sum += num;}}return parts;}}
packageclass051;// 机器人跳跃问题// 机器人正在玩一个古老的基于DOS的游戏// 游戏中有N+1座建筑,从0到N编号,从左到右排列// 编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位// 起初机器人在编号为0的建筑处// 每一步,它跳到下一个(右边)建筑。假设机器人在第k个建筑,且它现在的能量值是E// 下一步它将跳到第个k+1建筑// 它将会得到或者失去正比于与H(k+1)与E之差的能量// 如果 H(k+1) > E 那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值// 游戏目标是到达第个N建筑,在这个过程中,能量值不能为负数个单位// 现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏// 测试链接 : https://www.nowcoder.com/practice/7037a3d57bbd4336856b8e16a9cafd71// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode03_RobotPassThroughBuilding{publicstaticintMAXN=100001;publicstaticint[] arr =newint[MAXN];publicstaticint n;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;int l =0;int r =0;for(int i =1; i <= n; i++){
in.nextToken();
arr[i]=(int) in.nval;
r =Math.max(r, arr[i]);}
out.println(compute(l, r, r));}
out.flush();
out.close();
br.close();}// [l,r]通关所需最小能量的范围,不停二分// max是所有建筑的最大高度// 时间复杂度O(n * log(max)),额外空间复杂度O(1)publicstaticintcompute(int l,int r,int max){int m, ans =-1;while(l <= r){// m中点,此时通关所需规定的初始能量
m = l +((r - l)>>1);if(f(m, max)){
ans = m;
r = m -1;}else{
l = m +1;}}return ans;}// 初始能量为energy,max是建筑的最大高度,返回能不能通关// 为什么要给定建筑的最大高度?publicstaticbooleanf(int energy,int max){// 注意!// 如果给的能量值很大,那么后续能量增长将非常恐怖// 完全有可能超出long的范围// 所以要在遍历时,一定要加入energy >= max的判断// 一旦能量超过高度最大值,后面肯定通关了,可以提前返回了// 这里很阴for(int i =1; i <= n; i++){if(energy <= arr[i]){
energy -= arr[i]- energy;}else{
energy += energy - arr[i];}if(energy >= max){returntrue;}if(energy <0){returnfalse;}}returntrue;}}
packageclass051;importjava.util.Arrays;// 找出第K小的数对距离// 数对 (a,b) 由整数 a 和 b 组成,其数对距离定义为 a 和 b 的绝对差值。// 给你一个整数数组 nums 和一个整数 k// 数对由 nums[i] 和 nums[j] 组成且满足 0 <= i < j < nums.length// 返回 所有数对距离中 第 k 小的数对距离。// 测试链接 : https://leetcode.cn/problems/find-k-th-smallest-pair-distance/publicclassCode04_FindKthSmallestPairDistance{// 时间复杂度O(n * log(n) + log(max-min) * n),额外空间复杂度O(1)publicstaticintsmallestDistancePair(int[] nums,int k){int n = nums.length;Arrays.sort(nums);int ans =0;// [0, 最大-最小],不停二分for(int l =0, r = nums[n -1]- nums[0], m, cnt; l <= r;){// m中点,arr中任意两数的差值 <= m
m = l +((r - l)>>1);// 返回数字对的数量
cnt =f(nums, m);if(cnt >= k){
ans = m;
r = m -1;}else{
l = m +1;}}return ans;}// arr中任意两数的差值 <= limit// 这样的数字配对,有几对?publicstaticintf(int[] arr,int limit){int ans =0;// O(n)for(int l =0, r =0; l < arr.length; l++){// l......r r+1while(r +1< arr.length && arr[r +1]- arr[l]<= limit){
r++;}// arr[l...r]范围上的数差值的绝对值都不超过limit// arr[0...3]// 0,1// 0,2// 0,3
ans += r - l;}return ans;}}
packageclass051;// 同时运行N台电脑的最长时间// 你有 n 台电脑。给你整数 n 和一个下标从 0 开始的整数数组 batteries// 其中第 i 个电池可以让一台电脑 运行 batteries[i] 分钟// 你想使用这些电池让 全部 n 台电脑 同时 运行。// 一开始,你可以给每台电脑连接 至多一个电池// 然后在任意整数时刻,你都可以将一台电脑与它的电池断开连接,并连接另一个电池,你可以进行这个操作 任意次// 新连接的电池可以是一个全新的电池,也可以是别的电脑用过的电池// 断开连接和连接新的电池不会花费任何时间。// 注意,你不能给电池充电。// 请你返回你可以让 n 台电脑同时运行的 最长 分钟数。// 测试链接 : https://leetcode.cn/problems/maximum-running-time-of-n-computers/publicclassCode05_MaximumRunningTimeOfNComputers{// 单纯的二分答案法// 提交时把函数名改为maxRunTime// 时间复杂度O(n * log(sum)),额外空间复杂度O(1)publicstaticlongmaxRunTime1(int num,int[] arr){long sum =0;for(int x : arr){
sum += x;}long ans =0;// [0, sum],不停二分for(long l =0, r = sum, m; l <= r;){// m中点,让num台电脑共同运行m分钟,能不能做到
m = l +((r - l)>>1);if(f1(arr, num, m)){
ans = m;
l = m +1;}else{
r = m -1;}}return ans;}// 让num台电脑共同运行time分钟,能不能做到publicstaticbooleanf1(int[] arr,int num,long time){// 碎片电量总和long sum =0;for(int x : arr){if(x > time){
num--;}else{// x <= time,是碎片电池
sum += x;}if(sum >=(long) num * time){// 碎片电量 >= 台数 * 要求returntrue;}}returnfalse;}// 二分答案法 + 增加分析(贪心)// 提交时把函数名改为maxRunTime// 时间复杂度O(n * log(max)),额外空间复杂度O(1)publicstaticlongmaxRunTime2(int num,int[] arr){int max =0;long sum =0;for(int x : arr){
max =Math.max(max, x);
sum += x;}// 就是增加了这里的逻辑if(sum >(long) max * num){// 所有电池的最大电量是max// 如果此时sum > (long) max * num,// 说明 : 最终的供电时间一定在 >= max,而如果最终的供电时间 >= max// 说明 : 对于最终的答案X来说,所有电池都是课上讲的"碎片拼接"的概念// 那么寻找 ? * num <= sum 的情况中,尽量大的 ? 即可// 即sum / numreturn sum / num;}// 最终的供电时间一定在 < max范围上// [0, sum]二分范围,可能定的比较粗,虽然不影响,但毕竟是有点慢// [0, max]二分范围!更精细的范围,二分次数会变少int ans =0;for(int l =0, r = max, m; l <= r;){
m = l +((r - l)>>1);if(f2(arr, num, m)){
ans = m;
l = m +1;}else{
r = m -1;}}return ans;}publicstaticbooleanf2(int[] arr,int num,int time){// 碎片电量总和long sum =0;for(int x : arr){if(x > time){
num--;}else{
sum += x;}if(sum >=(long) num * time){returntrue;}}returnfalse;}}
packageclass051;importjava.util.PriorityQueue;// 计算等位时间// 给定一个数组arr长度为n,表示n个服务员,每服务一个人的时间// 给定一个正数m,表示有m个人等位,如果你是刚来的人,请问你需要等多久?// 假设m远远大于n,比如n <= 10^3, m <= 10^9,该怎么做是最优解?// 谷歌的面试,这个题连考了2个月// 找不到测试链接,所以用对数器验证publicclassCode06_WaitingTime{// 堆模拟// 验证方法,不是重点// 如果m很大,该方法会超时// 时间复杂度O(m * log(n)),额外空间复杂度O(n)publicstaticintwaitingTime1(int[] arr,int m){// 一个一个对象int[]// [醒来时间,服务一个客人要多久]PriorityQueue<int[]> heap =newPriorityQueue<>((a, b)->(a[0]- b[0]));int n = arr.length;for(int i =0; i < n; i++){
heap.add(newint[]{0, arr[i]});}for(int i =0; i < m; i++){int[] cur = heap.poll();
cur[0]+= cur[1];
heap.add(cur);}return heap.peek()[0];}// 二分答案法// 最优解// 时间复杂度O(n * log(min * w)),额外空间复杂度O(1)publicstaticintwaitingTime2(int[] arr,int w){int min =Integer.MAX_VALUE;for(int x : arr){
min =Math.min(min, x);}int ans =0;for(int l =0, r = min * w, m; l <= r;){// m中点,表示一定要让服务员工作的时间!
m = l +((r - l)>>1);// 能够给几个客人提供服务if(f(arr, m)>= w +1){
ans = m;
r = m -1;}else{
l = m +1;}}return ans;}// 如果每个服务员工作time,可以接待几位客人(结束的、开始的客人都算)publicstaticintf(int[] arr,int time){int ans =0;for(int num : arr){
ans +=(time / num)+1;}return ans;}// 对数器测试publicstaticvoidmain(String[] args){System.out.println("测试开始");intN=50;intV=30;intM=3000;int testTime =20000;for(int i =0; i < testTime; i++){int n =(int)(Math.random()*N)+1;int[] arr =randomArray(n,V);int m =(int)(Math.random()*M);int ans1 =waitingTime1(arr, m);int ans2 =waitingTime2(arr, m);if(ans1 != ans2){System.out.println("出错了!");}}System.out.println("测试结束");}// 对数器测试publicstaticint[]randomArray(int n,int v){int[] arr =newint[n];for(int i =0; i < n; i++){
arr[i]=(int)(Math.random()* v)+1;}return arr;}}
packageclass051;// 刀砍毒杀怪兽问题// 怪兽的初始血量是一个整数hp,给出每一回合刀砍和毒杀的数值cuts和poisons// 第i回合如果用刀砍,怪兽在这回合会直接损失cuts[i]的血,不再有后续效果// 第i回合如果用毒杀,怪兽在这回合不会损失血量,但是之后每回合都损失poisons[i]的血量// 并且你选择的所有毒杀效果,在之后的回合都会叠加// 两个数组cuts、poisons,长度都是n,代表你一共可以进行n回合// 每一回合你只能选择刀砍或者毒杀中的一个动作// 如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了// 但是怪兽如果有中毒效果的话,那么怪兽依然会在血量耗尽的那回合死掉// 返回至少多少回合,怪兽会死掉// 数据范围 : // 1 <= n <= 10^5// 1 <= hp <= 10^9// 1 <= cuts[i]、poisons[i] <= 10^9// 本题来自真实大厂笔试,找不到测试链接,所以用对数器验证publicclassCode07_CutOrPoison{// 动态规划方法(只是为了验证)// 目前没有讲动态规划,所以不需要理解这个函数// 这个函数只是为了验证二分答案的方法是否正确的// 纯粹为了写对数器验证才设计的方法,血量比较大的时候会超时// 这个方法不做要求,此时并不需要理解,可以在学习完动态规划章节之后来看看这个函数publicstaticintfast1(int[] cuts,int[] poisons,int hp){int sum =0;for(int num : poisons){
sum += num;}int[][][] dp =newint[cuts.length][hp +1][sum +1];returnf1(cuts, poisons,0, hp,0, dp);}// 不做要求publicstaticintf1(int[] cuts,int[] poisons,int i,int r,int p,int[][][] dp){
r -= p;if(r <=0){return i +1;}if(i == cuts.length){if(p ==0){returnInteger.MAX_VALUE;}else{return cuts.length +1+(r + p -1)/ p;}}if(dp[i][r][p]!=0){return dp[i][r][p];}int p1 = r <= cuts[i]?(i +1):f1(cuts, poisons, i +1, r - cuts[i], p, dp);int p2 =f1(cuts, poisons, i +1, r, p + poisons[i], dp);int ans =Math.min(p1, p2);
dp[i][r][p]= ans;return ans;}// 二分答案法// 最优解// 时间复杂度O(n * log(hp)),额外空间复杂度O(1)publicstaticintfast2(int[] cuts,int[] poisons,int hp){int ans =Integer.MAX_VALUE;for(int l =1, r = hp +1, m; l <= r;){// m中点,一定要让怪兽在m回合内死掉,更多回合无意义
m = l +((r - l)>>1);if(f(cuts, poisons, hp, m)){
ans = m;
r = m -1;}else{
l = m +1;}}return ans;}// cuts、posions,每一回合刀砍、毒杀的效果// hp:怪兽血量// limit:回合的限制publicstaticbooleanf(int[] cuts,int[] posions,long hp,int limit){int n =Math.min(cuts.length, limit);for(int i =0, j =1; i < n; i++, j++){
hp -=Math.max((long) cuts[i],(long)(limit - j)*(long) posions[i]);if(hp <=0){returntrue;}}returnfalse;}// 对数器测试publicstaticvoidmain(String[] args){// 随机测试的数据量不大// 因为数据量大了,fast1方法会超时// 所以在数据量不大的情况下,验证fast2方法功能正确即可// fast2方法在大数据量的情况下一定也能通过// 因为时间复杂度就是最优的System.out.println("测试开始");intN=30;intV=20;intH=300;int testTimes =10000;for(int i =0; i < testTimes; i++){int n =(int)(Math.random()*N)+1;int[] cuts =randomArray(n,V);int[] posions =randomArray(n,V);int hp =(int)(Math.random()*H)+1;int ans1 =fast1(cuts, posions, hp);int ans2 =fast2(cuts, posions, hp);if(ans1 != ans2){System.out.println("出错了!");}}System.out.println("测试结束");}// 对数器测试publicstaticint[]randomArray(int n,int v){int[] ans =newint[n];for(int i =0; i < n; i++){
ans[i]=(int)(Math.random()* v)+1;}return ans;}}
52. (必备)单调栈上
packageclass052;// 单调栈求每个位置左右两侧,离当前位置最近、且值严格小于的位置// 给定一个可能含有重复值的数组 arr// 找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 小的位置// 返回所有位置相应的信息。// 输入描述:// 第一行输入一个数字 n,表示数组 arr 的长度。// 以下一行输入 n 个数字,表示数组的值// 输出描述:// 输出n行,每行两个数字 L 和 R,如果不存在,则值为 -1,下标从 0 开始。// 测试链接 : https://www.nowcoder.com/practice/2a2c00e7a88a498693568cef63a4b7bb// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode01_LeftRightLess{publicstaticintMAXN=1000001;publicstaticint[] arr =newint[MAXN];publicstaticint[] stack =newint[MAXN];publicstaticint[][] ans =newint[MAXN][2];publicstaticint n, r;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;for(int i =0; i < n; i++){
in.nextToken();
arr[i]=(int) in.nval;}compute();for(int i =0; i < n; i++){
out.println(ans[i][0]+" "+ ans[i][1]);}}
out.flush();
out.close();
br.close();}// arr[0...n-1]publicstaticvoidcompute(){
r =0;int cur;// 遍历阶段for(int i =0; i < n; i++){// i -> arr[i]while(r >0&& arr[stack[r -1]]>= arr[i]){
cur = stack[--r];// cur当前弹出的位置,左边最近且小
ans[cur][0]= r >0? stack[r -1]:-1;
ans[cur][1]= i;}
stack[r++]= i;}// 清算阶段while(r >0){
cur = stack[--r];
ans[cur][0]= r >0? stack[r -1]:-1;
ans[cur][1]=-1;}// 修正阶段// 左侧的答案不需要修正一定是正确的,只有右侧答案需要修正// 从右往左修正,n-1位置的右侧答案一定是-1,不需要修正for(int i = n -2; i >=0; i--){if(ans[i][1]!=-1&& arr[ans[i][1]]== arr[i]){
ans[i][1]= ans[ans[i][1]][1];}}}}
packageclass052;// 每日温度// 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer// 其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后// 如果气温在这之后都不会升高,请在该位置用 0 来代替。// 测试链接 : https://leetcode.cn/problems/daily-temperatures/publicclassCode02_DailyTemperatures{publicstaticintMAXN=100001;publicstaticint[] stack =newint[MAXN];publicstaticint r;publicstaticint[]dailyTemperatures(int[] nums){int n = nums.length;int[] ans =newint[n];
r =0;for(int i =0, cur; i < n; i++){// 相等时候的处理,相等也加入单调栈while(r >0&& nums[stack[r -1]]< nums[i]){
cur = stack[--r];
ans[cur]= i - cur;}
stack[r++]= i;}return ans;}}
packageclass052;// 子数组的最小值之和// 给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。// 由于答案可能很大,因此 返回答案模 10^9 + 7// 测试链接 : https://leetcode.cn/problems/sum-of-subarray-minimums/publicclassCode03_SumOfSubarrayMinimums{publicstaticintMOD=1000000007;publicstaticintMAXN=30001;publicstaticint[] stack =newint[MAXN];publicstaticint r;publicstaticintsumSubarrayMins(int[] arr){long ans =0;
r =0;// 注意课上讲的相等情况的修正for(int i =0; i < arr.length; i++){while(r >0&& arr[stack[r -1]]>= arr[i]){int cur = stack[--r];int left = r ==0?-1: stack[r -1];
ans =(ans +(long)(cur - left)*(i - cur)* arr[cur])%MOD;}
stack[r++]= i;}while(r >0){int cur = stack[--r];int left = r ==0?-1: stack[r -1];
ans =(ans +(long)(cur - left)*(arr.length - cur)* arr[cur])%MOD;}return(int) ans;}}
packageclass052;// 柱状图中最大的矩形// 给定 n 个非负整数,用来表示柱状图中各个柱子的高度// 每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积// 测试链接:https://leetcode.cn/problems/largest-rectangle-in-histogrampublicclassCode04_LargestRectangleInHistogram{publicstaticintMAXN=100001;publicstaticint[] stack =newint[MAXN];publicstaticint r;publicstaticintlargestRectangleArea(int[] height){int n = height.length;
r =0;int ans =0, cur, left;for(int i =0; i < n; i++){// i -> arr[i]while(r >0&& height[stack[r -1]]>= height[i]){
cur = stack[--r];
left = r ==0?-1: stack[r -1];
ans =Math.max(ans, height[cur]*(i - left -1));}
stack[r++]= i;}while(r >0){
cur = stack[--r];
left = r ==0?-1: stack[r -1];
ans =Math.max(ans, height[cur]*(n - left -1));}return ans;}}
packageclass052;importjava.util.Arrays;// 最大矩形// 给定一个仅包含 0 和 1 、大小为 rows * cols 的二维二进制矩阵// 找出只包含 1 的最大矩形,并返回其面积// 测试链接:https://leetcode.cn/problems/maximal-rectangle/publicclassCode05_MaximalRectangle{publicstaticintMAXN=201;publicstaticint[] height =newint[MAXN];publicstaticint[] stack =newint[MAXN];publicstaticint r;publicstaticintmaximalRectangle(char[][] grid){int n = grid.length;int m = grid[0].length;Arrays.fill(height,0, m,0);int ans =0;for(int i =0; i < n; i++){// 来到i行,长方形一定要以i行做底!// 加工高度数组(压缩数组)for(int j =0; j < m; j++){
height[j]= grid[i][j]=='0'?0: height[j]+1;}
ans =Math.max(largestRectangleArea(m), ans);}return ans;}publicstaticintlargestRectangleArea(int m){
r =0;int ans =0, cur, left;for(int i =0; i < m; i++){// i -> arr[i]while(r >0&& height[stack[r -1]]>= height[i]){
cur = stack[--r];
left = r ==0?-1: stack[r -1];
ans =Math.max(ans, height[cur]*(i - left -1));}
stack[r++]= i;}while(r >0){
cur = stack[--r];
left = r ==0?-1: stack[r -1];
ans =Math.max(ans, height[cur]*(m - left -1));}return ans;}}
packageclass052;// 课上没讲的代码,单调栈在洛谷上的测试,原理是一样的// 洛谷上这道题对java特别不友好,不这么写通过不了,注意看注释,非常极限// 建议看看就好,现在的笔试和比赛时,不会这么极限的// 给定一个长度为n的数组,打印每个位置的右侧,大于该位置数字的最近位置// 测试链接 : https://www.luogu.com.cn/problem/P5788// 提交以下的code,提交时请把类名改成"Main",可以通过所有用例importjava.io.BufferedInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.PrintWriter;publicclassCode06_MonotonicStackLuogu{publicstaticvoidmain(String[] args)throwsIOException{int n =nextInt();int[] arr =newint[n +1];for(int i =1; i <= n; i++){
arr[i]=nextInt();}// 单调栈中保证 : 左 >= 右int[] stack =newint[n +1];int r =0;// 注意,这里为了省空间,直接复用了arr// 比如一个位置x,如果从stack中弹出,并且是当前的i位置让其弹出的// 那么令arr[x] = i,也就是代码36行// 此时arr[x]不再表示原始数组x位置的值// 而去表示,原始数组中,x的右边,大于arr[x],最近的位置// 也就是说,重新复用arr,让其变成答案数组// 为啥这么节省?为啥不单独弄一个答案数组// 没办法,不这么节省通过不了测试,空间卡的非常极限for(int i =1; i <= n; i++){while(r >0&& arr[stack[r -1]]< arr[i]){
arr[stack[--r]]= i;}
stack[r++]= i;}while(r >0){
arr[stack[--r]]=0;}
out.print(arr[1]);for(int i =2; i <= n; i++){
out.print(" "+ arr[i]);}
out.println();
out.flush();}// 用如下的方式读数据其实并不推荐// 但是这道题特别卡空间// 需要这么读数据让内存开销最小// 一般笔试、比赛时不需要这么写publicstaticInputStream in =newBufferedInputStream(System.in);publicstaticPrintWriter out =newPrintWriter(System.out);publicstaticintnextInt()throwsIOException{int ch, sign =1, ans =0;while(!Character.isDigit(ch = in.read())){if(ch =='-')
sign =-1;}do{
ans = ans *10+ ch -'0';}while(Character.isDigit(ch = in.read()));return(ans * sign);}}
53. (必备)单调栈下
packageclass053;// 最大宽度坡// 给定一个整数数组 A,坡是元组 (i, j),其中 i < j 且 A[i] <= A[j]// 这样的坡的宽度为 j - i,找出 A 中的坡的最大宽度,如果不存在,返回 0// 测试链接 : https://leetcode.cn/problems/maximum-width-ramp/publicclassCode01_MaximumWidthRamp{publicstaticintMAXN=50001;publicstaticint[] stack =newint[MAXN];publicstaticint r;publicstaticintmaxWidthRamp(int[] arr){// 令r=1相当于0位置进栈了// stack[0] = 0,然后栈的大小变成1
r =1;int n = arr.length;for(int i =1; i < n; i++){if(arr[stack[r -1]]> arr[i]){
stack[r++]= i;}}int ans =0;for(int j = n -1; j >=0; j--){while(r >0&& arr[stack[r -1]]<= arr[j]){
ans =Math.max(ans, j - stack[--r]);}}return ans;}}
packageclass053;importjava.util.Arrays;// 去除重复字母保证剩余字符串的字典序最小// 给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次// 需保证 返回结果的字典序最小// 要求不能打乱其他字符的相对位置// 测试链接 : https://leetcode.cn/problems/remove-duplicate-letters/publicclassCode02_RemoveDuplicateLetters{publicstaticintMAXN=26;// 每种字符词频publicstaticint[] cnts =newint[MAXN];// 每种字符目前有没有进栈publicstaticboolean[] enter =newboolean[MAXN];// 单调栈publicstaticchar[] stack =newchar[MAXN];publicstaticint r;publicstaticStringremoveDuplicateLetters(String str){
r =0;Arrays.fill(cnts,0);Arrays.fill(enter,false);char[] s = str.toCharArray();for(char cha : s){
cnts[cha -'a']++;}for(char cur : s){// 从左往右依次遍历字符,a -> 0 b -> 1 ... z -> 25// cur -> cur - 'a'if(!enter[cur -'a']){while(r >0&& stack[r -1]> cur && cnts[stack[r -1]-'a']>0){
enter[stack[r -1]-'a']=false;
r--;}
stack[r++]= cur;
enter[cur -'a']=true;}
cnts[cur -'a']--;}returnString.valueOf(stack,0, r);}}
packageclass053;// 大鱼吃小鱼问题// 给定一个数组arr,每个值代表鱼的体重// 每一轮每条鱼都会吃掉右边离自己最近比自己体重小的鱼,每条鱼向右找只吃一条// 但是吃鱼这件事是同时发生的,也就是同一轮在A吃掉B的同时,A也可能被别的鱼吃掉// 如果有多条鱼在当前轮找到的是同一条小鱼,那么在这一轮,这条小鱼同时被这些大鱼吃// 请问多少轮后,鱼的数量就固定了// 比如 : 8 3 1 5 6 7 2 4// 第一轮 : 8吃3;3吃1;5、6、7吃2;4没有被吃。数组剩下 8 5 6 7 4// 第二轮 : 8吃5;5、6、7吃4。数组剩下 8 6 7// 第三轮 : 8吃6。数组剩下 8 7// 第四轮 : 8吃7。数组剩下 8。// 过程结束,返回4// 测试链接 : https://www.nowcoder.com/practice/77199defc4b74b24b8ebf6244e1793de// 测试链接 : https://leetcode.cn/problems/steps-to-make-array-non-decreasing/importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode03_BigFishEatSmallFish{publicstaticintMAXN=100001;publicstaticint[] arr =newint[MAXN];publicstaticint n;publicstaticint[][] stack =newint[MAXN][2];publicstaticint r;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;for(int i =0; i < n; i++){
in.nextToken();
arr[i]=(int) in.nval;}
out.println(turns());}
out.flush();
out.close();
br.close();}// arr[0...n-1]鱼的体重// stack[...]随便用publicstaticintturns(){
r =0;int ans =0;for(int i = n -1, curTurns; i >=0; i--){// i号鱼,arr[i]// 0轮是初始
curTurns =0;while(r >0&& stack[r -1][0]< arr[i]){
curTurns =Math.max(curTurns +1, stack[--r][1]);}
stack[r][0]= arr[i];
stack[r++][1]= curTurns;
ans =Math.max(ans, curTurns);}return ans;}// 也找到了leetcode测试链接// 测试链接 : https://leetcode.cn/problems/steps-to-make-array-non-decreasing/// 提交如下代码,可以直接通过publicstaticintMAXM=100001;publicstaticint[][] s =newint[MAXM][2];publicstaticint size;publicstaticinttotalSteps(int[] arr){
size =0;int ans =0;for(int i = arr.length -1, curTurns; i >=0; i--){
curTurns =0;while(size >0&& s[size -1][0]< arr[i]){
curTurns =Math.max(curTurns +1, s[--size][1]);}
s[size][0]= arr[i];
s[size++][1]= curTurns;
ans =Math.max(ans, curTurns);}return ans;}}
packageclass053;importjava.util.Arrays;// 统计全1子矩形的数量// 给你一个 m * n 的矩阵 mat,其中只有0和1两种值// 请你返回有多少个 子矩形 的元素全部都是1// 测试链接 : https://leetcode.cn/problems/count-submatrices-with-all-ones/publicclassCode04_CountSubmatricesWithAllOnes{publicstaticintMAXM=151;publicstaticint[] height =newint[MAXM];publicstaticint[] stack =newint[MAXM];publicstaticint r;publicstaticintnumSubmat(int[][] mat){int n = mat.length;int m = mat[0].length;int ans =0;Arrays.fill(height,0, m,0);for(int i =0; i < n; i++){for(int j =0; j < m; j++){
height[j]= mat[i][j]==0?0: height[j]+1;}
ans +=countFromBottom(m);}return ans;}// 比如// 1// 1// 1 1// 1 1 1// 1 1 1// 1 1 1// // 3 .... 6 .... 8// left cur i// 如上图,假设6位置从栈中弹出,6位置的高度为6(上面6个1)// 6位置的左边、离6位置最近、且小于高度6的是3位置(left),3位置的高度是3// 6位置的右边、离6位置最近、且小于高度6的是8位置(i),8位置的高度是4// 此时我们求什么?// 1) 求在4~7范围上必须以高度6作为高的矩形有几个?// 2) 求在4~7范围上必须以高度5作为高的矩形有几个?// 也就是说,<=4的高度一律不求,>6的高度一律不求!// 其他位置也会从栈里弹出,等其他位置弹出的时候去求!// 那么在4~7范围上必须以高度6作为高的矩形有几个?如下:// 4..4 4..5 4..6 4..7// 5..5 5..6 5..7// 6..6 6..7// 7..7// 10个!什么公式?// 4...7范围的长度为4,那么数量就是 : 4*5/2// 同理在4~7范围上,必须以高度5作为高的矩形也是这么多// 所以cur从栈里弹出时产生的数量 : // (cur位置的高度-Max{left位置的高度,i位置的高度}) * ((i-left-1)*(i-left)/2)publicstaticintcountFromBottom(int m){// height[0...m-1]
r =0;int ans =0;for(int i =0, left, len, bottom; i < m; i++){while(r >0&& height[stack[r -1]]>= height[i]){int cur = stack[--r];if(height[cur]> height[i]){// 只有height[cur] > height[i]才结算// 如果是因为height[cur]==height[i]导致cur位置从栈中弹出// 那么不结算!等i位置弹出的时候再说!// 上一节课讲了很多这种相等时候的处理,比如"柱状图中最大的矩形"问题
left = r ==0?-1: stack[r -1];
len = i - left -1;
bottom =Math.max(left ==-1?0: height[left], height[i]);
ans +=(height[cur]- bottom)* len *(len +1)/2;}}
stack[r++]= i;}while(r >0){int cur = stack[--r];int left = r ==0?-1: stack[r -1];int len = m - left -1;int down = left ==-1?0: height[left];
ans +=(height[cur]- down)* len *(len +1)/2;}return ans;}}
54. (必备)单调队列上
packageclass054;// 滑动窗口最大值(单调队列经典用法模版)// 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧// 你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。// 返回 滑动窗口中的最大值 。// 测试链接 : https://leetcode.cn/problems/sliding-window-maximum/publicclassCode01_SlidingWindowMaximum{publicstaticintMAXN=100001;publicstaticint[] deque =newint[MAXN];publicstaticint h, t;publicstaticint[]maxSlidingWindow(int[] arr,int k){
h = t =0;int n = arr.length;// 先形成长度为k-1的窗口for(int i =0; i < k -1; i++){// 大 -> 小while(h < t && arr[deque[t -1]]<= arr[i]){
t--;}
deque[t++]= i;}int m = n - k +1;int[] ans =newint[m];// 当前窗口k-1长度for(int l =0, r = k -1; l < m; l++, r++){// 少一个,要让r位置的数进来while(h < t && arr[deque[t -1]]<= arr[r]){
t--;}
deque[t++]= r;// 收集答案
ans[l]= arr[deque[h]];// l位置的数出去if(deque[h]== l){
h++;}}return ans;}}
packageclass054;// 绝对差不超过限制的最长连续子数组// 给你一个整数数组 nums ,和一个表示限制的整数 limit// 请你返回最长连续子数组的长度// 该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit// 如果不存在满足条件的子数组,则返回 0// 测试链接 : https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/publicclassCode02_LongestSubarrayAbsoluteLimit{publicstaticintMAXN=100001;// 窗口内最大值的更新结构(单调队列)publicstaticint[] maxDeque =newint[MAXN];// 窗口内最小值的更新结构(单调队列)publicstaticint[] minDeque =newint[MAXN];publicstaticint maxh, maxt, minh, mint;publicstaticint[] arr;publicstaticintlongestSubarray(int[] nums,int limit){
maxh = maxt = minh = mint =0;
arr = nums;int n = arr.length;int ans =0;for(int l =0, r =0; l < n; l++){// [l,r),r永远是没有进入窗口的、下一个数所在的位置while(r < n &&ok(limit, nums[r])){push(r++);}// 从while出来的时候,[l,r)是l开头的子数组能向右延伸的最大范围
ans =Math.max(ans, r - l);pop(l);}return ans;}// 判断如果加入数字number,窗口最大值 - 窗口最小值是否依然 <= limit// 依然 <= limit,返回true// 不再 <= limit,返回falsepublicstaticbooleanok(int limit,int number){// max : 如果number进来,新窗口的最大值int max = maxh < maxt ?Math.max(arr[maxDeque[maxh]], number): number;// min : 如果number进来,新窗口的最小值int min = minh < mint ?Math.min(arr[minDeque[minh]], number): number;return max - min <= limit;}// r位置的数字进入窗口,修改窗口内最大值的更新结构、修改窗口内最小值的更新结构publicstaticvoidpush(int r){while(maxh < maxt && arr[maxDeque[maxt -1]]<= arr[r]){
maxt--;}
maxDeque[maxt++]= r;while(minh < mint && arr[minDeque[mint -1]]>= arr[r]){
mint--;}
minDeque[mint++]= r;}// 窗口要吐出l位置的数了!检查过期!publicstaticvoidpop(int l){if(maxh < maxt && maxDeque[maxh]== l){
maxh++;}if(minh < mint && minDeque[minh]== l){
minh++;}}}
packageclass054;// 接取落水的最小花盆// 老板需要你帮忙浇花。给出 N 滴水的坐标,y 表示水滴的高度,x 表示它下落到 x 轴的位置// 每滴水以每秒1个单位长度的速度下落。你需要把花盆放在 x 轴上的某个位置// 使得从被花盆接着的第 1 滴水开始,到被花盆接着的最后 1 滴水结束,之间的时间差至少为 D// 我们认为,只要水滴落到 x 轴上,与花盆的边沿对齐,就认为被接住// 给出 N 滴水的坐标和 D 的大小,请算出最小的花盆的宽度 W// 测试链接 : https://www.luogu.com.cn/problem/P2698// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;importjava.util.Arrays;publicclassCode03_FallingWaterSmallestFlowerPot{publicstaticintMAXN=100005;publicstaticint[][] arr =newint[MAXN][2];publicstaticint n, d;publicstaticint[] maxDeque =newint[MAXN];publicstaticint[] minDeque =newint[MAXN];publicstaticint maxh, maxt, minh, mint;publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;
in.nextToken();
d =(int) in.nval;for(int i =0; i < n; i++){
in.nextToken();
arr[i][0]=(int) in.nval;
in.nextToken();
arr[i][1]=(int) in.nval;}int ans =compute();
out.println(ans ==Integer.MAX_VALUE?-1: ans);}
out.flush();
out.close();
br.close();}publicstaticintcompute(){// arr[0...n-1][2]: x(0), 高度(1)// 所有水滴根据x排序,谁小谁在前Arrays.sort(arr,0, n,(a, b)-> a[0]- b[0]);
maxh = maxt = minh = mint =0;int ans =Integer.MAX_VALUE;for(int l =0, r =0; l < n; l++){// [l,r) : 水滴的编号// l : 当前花盘的左边界,arr[l][0]while(!ok()&& r < n){push(r++);}if(ok()){
ans =Math.min(ans, arr[r -1][0]- arr[l][0]);}pop(l);}return ans;}// 当前窗口 最大值 - 最小值 是不是>=dpublicstaticbooleanok(){int max = maxh < maxt ? arr[maxDeque[maxh]][1]:0;int min = minh < mint ? arr[minDeque[minh]][1]:0;return max - min >= d;}publicstaticvoidpush(int r){while(maxh < maxt && arr[maxDeque[maxt -1]][1]<= arr[r][1]){
maxt--;}
maxDeque[maxt++]= r;while(minh < mint && arr[minDeque[mint -1]][1]>= arr[r][1]){
mint--;}
minDeque[mint++]= r;}publicstaticvoidpop(int l){if(maxh < maxt && maxDeque[maxh]== l){
maxh++;}if(minh < mint && minDeque[minh]== l){
minh++;}}}
55. (必备)单调队列下
packageclass055;// 和至少为K的最短子数组// 给定一个数组arr,其中的值有可能正、负、0// 给定一个正数k// 返回累加和>=k的所有子数组中,最短的子数组长度// 测试链接 : https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/publicclassCode01_ShortestSubarrayWithSumAtLeastK{publicstaticintMAXN=100001;// sum[0] : 前0个数的前缀和// sum[i] : 前i个数的前缀和publicstaticlong[] sum =newlong[MAXN];publicstaticint[] deque =newint[MAXN];publicstaticint h, t;publicstaticintshortestSubarray(int[] arr,intK){int n = arr.length;for(int i =0; i < n; i++){// [3,4,5]// 0 1 2// sum[0] = 0// sum[1] = 3// sum[2] = 7// sum[3] = 12
sum[i +1]= sum[i]+ arr[i];}
h = t =0;int ans =Integer.MAX_VALUE;for(int i =0; i <= n; i++){// 前0个数前缀和// 前1个数前缀和// 前2个数前缀和// ...// 前n个数前缀和while(h != t && sum[i]- sum[deque[h]]>=K){// 如果当前的前缀和 - 头前缀和,达标!
ans =Math.min(ans, i - deque[h++]);}// 前i个数前缀和,从尾部加入// 小 大while(h != t && sum[deque[t -1]]>= sum[i]){
t--;}
deque[t++]= i;}return ans !=Integer.MAX_VALUE? ans :-1;}}
packageclass055;// 满足不等式的最大值// 给你一个数组 points 和一个整数 k// 数组中每个元素都表示二维平面上的点的坐标,并按照横坐标 x 的值从小到大排序// 也就是说 points[i] = [xi, yi]// 并且在 1 <= i < j <= points.length 的前提下,xi < xj 总成立// 请你找出 yi + yj + |xi - xj| 的 最大值,// 其中 |xi - xj| <= k 且 1 <= i < j <= points.length// 题目测试数据保证至少存在一对能够满足 |xi - xj| <= k 的点。// 测试链接 : https://leetcode.cn/problems/max-value-of-equation/publicclassCode02_MaxValueOfEquation{publicstaticintMAXN=100001;// [、i号点[x,y]、]// h、tpublicstaticint[][] deque =newint[MAXN][2];publicstaticint h, t;// 已知所有的点都是根据x值排序的!// 任何两个点,组成指标,要求 : 后x - 前x <= k// 返回最大指标publicstaticintfindMaxValueOfEquation(int[][] points,int k){
h = t =0;int n = points.length;int ans =Integer.MIN_VALUE;for(int i =0, x, y; i < n; i++){// i号点是此时的点,当前的后面点,看之前哪个点的y-x值最大,x距离又不能超过k
x = points[i][0];
y = points[i][1];while(h < t && deque[h][0]+ k < x){// 单调队列头部的可能性过期了,头部点的x与当前点x的距离超过了k
h++;}if(h < t){
ans =Math.max(ans, x + y + deque[h][1]- deque[h][0]);}// i号点的x和y,该从尾部进入单调队列// 大 -> 小while(h < t && deque[t -1][1]- deque[t -1][0]<= y - x){
t--;}
deque[t][0]= x;
deque[t++][1]= y;}return ans;}}
packageclass055;importjava.util.Arrays;// 你可以安排的最多任务数目// 给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成// 需要的力量值保存在下标从 0 开始的整数数组 tasks 中,// 第i个任务需要 tasks[i] 的力量才能完成// 每个工人的力量值保存在下标从 0 开始的整数数组workers中,// 第j个工人的力量值为 workers[j]// 每个工人只能完成一个任务,且力量值需要大于等于该任务的力量要求值,即workers[j]>=tasks[i]// 除此以外,你还有 pills 个神奇药丸,可以给 一个工人的力量值 增加 strength// 你可以决定给哪些工人使用药丸,但每个工人 最多 只能使用 一片 药丸// 给你下标从 0 开始的整数数组tasks 和 workers 以及两个整数 pills 和 strength// 请你返回 最多 有多少个任务可以被完成。// 测试链接 : https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign/publicclassCode03_MaximumNumberOfTasksYouCanAssign{publicstaticint[] tasks;publicstaticint[] workers;publicstaticintMAXN=50001;publicstaticint[] deque =newint[MAXN];publicstaticint h, t;// 两个数组排序 : O(n * logn) + O(m * logm)// 二分答案的过程,每次二分都用一下双端队列 : O((n和m最小值)*log(n和m最小值))// 最复杂的反而是排序的过程了,所以时间复杂度O(n * logn) + O(m * logm)publicstaticintmaxTaskAssign(int[] ts,int[] ws,int pills,int strength){
tasks = ts;
workers = ws;Arrays.sort(tasks);Arrays.sort(workers);int tsize = tasks.length;int wsize = workers.length;int ans =0;// [0, Math.min(tsize, wsize)]for(int l =0, r =Math.min(tsize, wsize), m; l <= r;){// m中点,一定要完成的任务数量
m =(l + r)/2;if(f(0, m -1, wsize - m, wsize -1, strength, pills)){
ans = m;
l = m +1;}else{
r = m -1;}}return ans;}// tasks[tl....tr]需要力量最小的几个任务// workers[wl....wr]力量值最大的几个工人// 药效是s,一共有的药pills个// 在药的数量不超情况下,能不能每个工人都做一个任务publicstaticbooleanf(int tl,int tr,int wl,int wr,int s,int pills){
h = t =0;// 已经使用的药的数量int cnt =0;for(int i = wl, j = tl; i <= wr; i++){// i : 工人的编号// j : 任务的编号for(; j <= tr && tasks[j]<= workers[i]; j++){// 工人不吃药的情况下,去解锁任务
deque[t++]= j;}if(h < t && tasks[deque[h]]<= workers[i]){
h++;}else{// 吃药之后的逻辑for(; j <= tr && tasks[j]<= workers[i]+ s; j++){
deque[t++]= j;}if(h < t){
cnt++;
t--;}else{returnfalse;}}}return cnt <= pills;}}
56. (必备)并查集上
packageclass056;// 并查集模版(牛客)// 路径压缩 + 小挂大// 测试链接 : https://www.nowcoder.com/practice/e7ed657974934a30b2010046536a5372// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode01_UnionFindNowCoder{publicstaticintMAXN=1000001;publicstaticint[] father =newint[MAXN];publicstaticint[] size =newint[MAXN];publicstaticint[] stack =newint[MAXN];publicstaticint n;publicstaticvoidbuild(){for(int i =0; i <= n; i++){
father[i]= i;
size[i]=1;}}// i号节点,往上一直找,找到代表节点返回!publicstaticintfind(int i){// 沿途收集了几个点int size =0;while(i != father[i]){
stack[size++]= i;
i = father[i];}// 沿途节点收集好了,i已经跳到代表节点了while(size >0){
father[stack[--size]]= i;}return i;}publicstaticbooleanisSameSet(int x,int y){returnfind(x)==find(y);}publicstaticvoidunion(int x,int y){int fx =find(x);int fy =find(y);if(fx != fy){// fx是集合的代表:拿大小// fy是集合的代表:拿大小if(size[fx]>= size[fy]){
size[fx]+= size[fy];
father[fy]= fx;}else{
size[fy]+= size[fx];
father[fx]= fy;}}}publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;build();
in.nextToken();int m =(int) in.nval;for(int i =0; i < m; i++){
in.nextToken();int op =(int) in.nval;
in.nextToken();int x =(int) in.nval;
in.nextToken();int y =(int) in.nval;if(op ==1){
out.println(isSameSet(x, y)?"Yes":"No");}else{union(x, y);}}}
out.flush();
out.close();
br.close();}}
packageclass056;// 并查集模版(洛谷)// 本实现用递归函数实现路径压缩,而且省掉了小挂大的优化,一般情况下可以省略// 测试链接 : https://www.luogu.com.cn/problem/P3367// 请同学们务必参考如下代码中关于输入、输出的处理// 这是输入输出处理效率很高的写法// 提交以下的code,提交时请把类名改成"Main",可以直接通过importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.StreamTokenizer;publicclassCode02_UnionFindLuogu{publicstaticintMAXN=10001;publicstaticint[] father =newint[MAXN];publicstaticint n;publicstaticvoidbuild(){for(int i =0; i <= n; i++){
father[i]= i;}}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticbooleanisSameSet(int x,int y){returnfind(x)==find(y);}publicstaticvoidunion(int x,int y){
father[find(x)]=find(y);}publicstaticvoidmain(String[] args)throwsIOException{BufferedReader br =newBufferedReader(newInputStreamReader(System.in));StreamTokenizer in =newStreamTokenizer(br);PrintWriter out =newPrintWriter(newOutputStreamWriter(System.out));while(in.nextToken()!=StreamTokenizer.TT_EOF){
n =(int) in.nval;build();
in.nextToken();int m =(int) in.nval;for(int i =0; i < m; i++){
in.nextToken();int z =(int) in.nval;
in.nextToken();int x =(int) in.nval;
in.nextToken();int y =(int) in.nval;if(z ==1){union(x, y);}else{
out.println(isSameSet(x, y)?"Y":"N");}}}
out.flush();
out.close();
br.close();}}
packageclass056;// 情侣牵手// n对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手// 人和座位由一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的ID// 情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)// 返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起// 每次交换可选择任意两人,让他们站起来交换座位// 测试链接 : https://leetcode.cn/problems/couples-holding-hands/publicclassCode03_CouplesHoldingHands{publicstaticintminSwapsCouples(int[] row){int n = row.length;build(n /2);for(int i =0; i < n; i +=2){union(row[i]/2, row[i +1]/2);}return n /2- sets;}publicstaticintMAXN=31;publicstaticint[] father =newint[MAXN];publicstaticint sets;publicstaticvoidbuild(int m){for(int i =0; i < m; i++){
father[i]= i;}
sets = m;}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticvoidunion(int x,int y){int fx =find(x);int fy =find(y);if(fx != fy){
father[fx]= fy;
sets--;}}}
packageclass056;// 相似字符串组// 如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等// 那么称 X 和 Y 两个字符串相似// 如果这两个字符串本身是相等的,那它们也是相似的// 例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置);// "rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars","rats",或 "arts" 相似// 总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}// 注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似// 形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。// 给你一个字符串列表 strs列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。// 返回 strs 中有多少字符串组// 测试链接 : https://leetcode.cn/problems/similar-string-groups/publicclassCode04_SimilarStringGroups{publicstaticintMAXN=301;publicstaticint[] father =newint[MAXN];publicstaticint sets;publicstaticvoidbuild(int n){for(int i =0; i < n; i++){
father[i]= i;}
sets = n;}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticvoidunion(int x,int y){int fx =find(x);int fy =find(y);if(fx != fy){
father[fx]= fy;
sets--;}}publicstaticintnumSimilarGroups(String[] strs){int n = strs.length;int m = strs[0].length();build(n);for(int i =0; i < n; i++){for(int j = i +1; j < n; j++){if(find(i)!=find(j)){int diff =0;for(int k =0; k < m && diff <3; k++){if(strs[i].charAt(k)!= strs[j].charAt(k)){
diff++;}}if(diff ==0|| diff ==2){union(i, j);}}}}return sets;}}
packageclass056;// 岛屿数量// 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量// 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成// 此外,你可以假设该网格的四条边均被水包围// 测试链接 : https://leetcode.cn/problems/number-of-islands/publicclassCode05_NumberOfIslands{// 并查集的做法publicstaticintnumIslands(char[][] board){int n = board.length;int m = board[0].length;build(n, m, board);for(int i =0; i < n; i++){for(int j =0; j < m; j++){if(board[i][j]=='1'){if(j >0&& board[i][j -1]=='1'){union(i, j, i, j -1);}if(i >0&& board[i -1][j]=='1'){union(i, j, i -1, j);}}}}return sets;}publicstaticintMAXSIZE=100001;publicstaticint[] father =newint[MAXSIZE];publicstaticint cols;publicstaticint sets;publicstaticvoidbuild(int n,int m,char[][] board){
cols = m;
sets =0;for(int a =0; a < n; a++){for(int b =0, index; b < m; b++){if(board[a][b]=='1'){
index =index(a, b);
father[index]= index;
sets++;}}}}publicstaticintindex(int a,int b){return a * cols + b;}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticvoidunion(int a,int b,int c,int d){int fx =find(index(a, b));int fy =find(index(c, d));if(fx != fy){
father[fx]= fy;
sets--;}}}
57. (必备)并查集下
packageclass057;importjava.util.HashMap;// 移除最多的同行或同列石头// n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头// 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头// 给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置// 返回 可以移除的石子 的最大数量。// 测试链接 : https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column/publicclassCode01_MostStonesRemovedWithSameRowOrColumn{// key : 某行// value : 第一次遇到的石头编号publicstaticHashMap<Integer,Integer> rowFirst =newHashMap<Integer,Integer>();publicstaticHashMap<Integer,Integer> colFirst =newHashMap<Integer,Integer>();publicstaticintMAXN=1001;publicstaticint[] father =newint[MAXN];publicstaticint sets;publicstaticvoidbuild(int n){
rowFirst.clear();
colFirst.clear();for(int i =0; i < n; i++){
father[i]= i;}
sets = n;}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticvoidunion(int x,int y){int fx =find(x);int fy =find(y);if(fx != fy){
father[fx]= fy;
sets--;}}publicstaticintremoveStones(int[][] stones){int n = stones.length;build(n);for(int i =0; i < n; i++){int row = stones[i][0];int col = stones[i][1];if(!rowFirst.containsKey(row)){
rowFirst.put(row, i);}else{union(i, rowFirst.get(row));}if(!colFirst.containsKey(col)){
colFirst.put(col, i);}else{union(i, colFirst.get(col));}}return n - sets;}}
packageclass057;importjava.util.ArrayList;importjava.util.Arrays;importjava.util.List;// 找出知晓秘密的所有专家// 给你一个整数 n ,表示有 n 个专家从 0 到 n - 1 编号// 另外给你一个下标从 0 开始的二维整数数组 meetings// 其中 meetings[i] = [xi, yi, timei] 表示专家 xi 和专家 yi 在时间 timei 要开一场会// 一个专家可以同时参加 多场会议 。最后,给你一个整数 firstPerson// 专家 0 有一个 秘密 ,最初,他在时间 0 将这个秘密分享给了专家 firstPerson// 接着,这个秘密会在每次有知晓这个秘密的专家参加会议时进行传播// 更正式的表达是,每次会议,如果专家 xi 在时间 timei 时知晓这个秘密// 那么他将会与专家 yi 分享这个秘密,反之亦然。秘密共享是 瞬时发生 的// 也就是说,在同一时间,一个专家不光可以接收到秘密,还能在其他会议上与其他专家分享// 在所有会议都结束之后,返回所有知晓这个秘密的专家列表// 你可以按 任何顺序 返回答案// 链接测试 : https://leetcode.cn/problems/find-all-people-with-secret/publicclassCode02_FindAllPeopleWithSecret{publicstaticintMAXN=100001;publicstaticint[] father =newint[MAXN];// 集合的标签信息 : 设置集合的一些属性// 属性在哪?secret[代表元素] 代表集合的属性publicstaticboolean[] secret =newboolean[MAXN];publicstaticvoidbuild(int n,int first){for(int i =0; i < n; i++){
father[i]= i;
secret[i]=false;}
father[first]=0;
secret[0]=true;}publicstaticintfind(int i){if(i != father[i]){
father[i]=find(father[i]);}return father[i];}publicstaticvoidunion(int x,int y){int fx =find(x);int fy =find(y);if(fx != fy){
father[fx]= fy;
secret[fy]|= secret[fx];}}// 会议排序 : m * log m// 处理过程 : O(m)// 收集答案 : O(n)publicstaticList<Integer>findAllPeople(int n,int[][] meetings,int first){build(n, first);// {0 : 专家 1 : 专家编号 2 : 时刻}Arrays.sort(meetings,(a, b)-> a[2]- b[2]);int m = meetings.length;for(int l =0, r; l < m;){
r = l;while(r +1< m && meetings[l][2]== meetings[r +1][2]){
r++;}// l....r这些会议,一定是一个时刻for(int i = l; i <= r; i++){union(meetings[i][0], meetings[i][1]);}// 有小的撤销行为,但这不是可撤销并查集// 只是每一批没有知道秘密的专家重新建立集合而已for(int i = l, a, b; i <= r; i++){
a = meetings[i][0];
b = meetings[i][1];if(!secret[find(a)]){
father[a]= a;}if(!secret[find(b)]){
father[b]= b;}}
l = r +1;}List<Integer> ans =newArrayList<>();for(int i =0; i < n; i++){if(secret[find(i)]){
ans.add(i);}}return ans;}}