目录
基础算法模板传送门:【基础算法模板梳理】再也不想学算法了!(待更新)-CSDN博客
P1 - 递归
92. 递归实现指数型枚举 - dfs枚举位置
思路:
题目要求枚举所有数的情况,则可以通过st[]数组标记已选位置,dfs从1开始枚举每一个位置u,一条路选该位置u的数字,另一条路不选该位置u的数字,当每一次枚举完所有位置(u>n)时,输出标记情况。
import java.util.*;
public class Main
{
static int n;
static boolean[] st=new boolean[20];
public static void dfs(int u)
{
if(u>n) //所有位置枚举完则输出该条路径的选数方案
{
for(int i=1;i<=n;i++)
if(st[i]) System.out.print(i+" ");
System.out.println();
return;
}
st[u]=true; //左分支选该位置上的数
dfs(u+1);
st[u]=false; //右分支不选该位置上的数
dfs(u+1);
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
dfs(1); //从位置1开始枚举
}
}
94. 递归实现排列型枚举 - dfs枚举位置和方案
思路:
题目要求枚举所有排列顺序,则dfs从1开始枚举每一个位置u,建立st[]数组记录每个数字的选择情况,用res[]记录选择方案。每一层递归枚举位置u,遍历所有数字 ,若该数字未被选择,则标记后放入res[u],进入下一层递归,枚举完一种情况后回溯,取消标记。
import java.util.*;
public class Main
{
static int n;
static boolean[] st=new boolean[10];
static int[] res=new int[10]; //用于保存方案
public static void dfs(int u)
{
if(u>n)
{
for(int i=1;i<=n;i++)
System.out.print(res[i]+" ");
System.out.println();
return;
}
for(int i=1;i<=n;i++) //枚举每一个数字
if(st[i]==false)
{
res[u]=i; //如果该数字未被标记,则放在当前位置u上
st[i]=true;
dfs(u+1);
st[i]=false;
}
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
dfs(1);
}
}
717. 简单斐波那契 - dfs / 滚动数组
import java.util.*;
public class Main
{
static int n;
public static int dfs(int n)
{
if(n==0||n==1) return n;
else return dfs(n-1) + dfs(n-2);
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
long x;
for(int i=0;i<n;i++)
{
x=dfs(i);
System.out.print(x+" ");
}
}
}
import java.util.*;
public class Main
{
static int n;
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
long a=0,b=1,c;
for(int i=0;i<n;i++)
{
System.out.print(a+" ");
c=a+b;
a=b;
b=c;
}
}
}
P2 - 二分
★二分模板
- l+r >> 1 —— 先r=mid —— 找左边界 —— 找大于等于某值的最小值
- l+r+1 >>1 —— 先l=mid —— 找右边界 —— 找小于等于某值的最大值
503. 借教室 - 二分+差分
思路:
按先后顺序借教室,则容易想到每个人在【每天已有空教室数】中减去【需要的教室数】,则可以用差分模板。
因为若某人的【所需空教室数】无法满足,则后面人的要求均无法满足,具有单调性,可以对每个人进行二分,check(mid)函数负责检验第mid人的需求是否被满足。
import java.util.*;
public class Main
{
static int n,m;
static int N=(int)1e6+10;
static int r[]=new int[N]; //每天可提供空教室数
static int d[]=new int[N]; //每个人每天所需教室数
static int s[]=new int[N]; //每个人租赁开始时间
static int t[]=new int[N]; //每个人租赁结束时间
static long b[]=new long[N]; //差分数组
public static boolean check(int mid) //如果第mid人不满足,则返回true
{
for(int i=1;i<=n;i++) b[i]=r[i]-r[i-1]; //构造差分数组
for(int i=1;i<=mid;i++) //差分 对空教室数按第1-mid人的需求进行减少
{
b[s[i]]-=d[i];
b[t[i]+1]+=d[i];
}
for(int i=1;i<=n;i++)
{
b[i]+=b[i-1];
if(b[i]<0) return true;
}
return false;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
for(int i=1;i<=n;i++) r[i]=sc.nextInt();
for(int i=1;i<=m;i++)
{
d[i]=sc.nextInt();
s[i]=sc.nextInt();
t[i]=sc.nextInt();
}
if(!check(m)) //如果最后一个人都满足,则所有订单均可满足
{
System.out.print(0);
return;
}
int l=1,r=m;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
System.out.println("-1");
System.out.print(r);
}
}
1227. 分巧克力 - 二分
思路:
若边长c不能满足分块需求,则大于c的都不能满足,具有单调性,可以二分边长
check(mid)函数负责判断边长mid是否可以满足分块需求
自己ac的呜呜呜,好久没写代码了
import java.util.*;
public class Main{
static int n,k;
static int N=(int) 1e5+10;
static int[] h=new int[N],w=new int[N];
public static boolean ck(int mid) //如果不满足则返回ture
{
int sum=0;
for(int i=0;i<n;i++)
{
if(h[i]<mid||w[i]<mid) continue;
sum+=(h[i]/mid)*(w[i]/mid);
}
if(sum>=k) return false;
return true;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
k=sc.nextInt();
int max=0;
for(int i=0;i<n;i++)
{
h[i]=sc.nextInt();
w[i]=sc.nextInt();
max=Math.max(h[i],Math.max(w[i],max));
}
int l=1,r=max;
while(l<r)
{
int mid=l+r>>1;
if(ck(mid)) r=mid;
else l=mid+1;
}
if(ck(r)) System.out.print(r-1);
else System.out.print(r);
}
}
5407. 管道 - 二分+区间合并
思路:
自己写的时候用二分+差分,但是数据范围爆了
因为若某时刻可以将管道灌满,则该时刻之后的任何时刻均不为答案,具有单调性,因此可以对时刻进行二分。因为差分会爆数据,所以采用区间合并,计算每一个阀门在时刻mid的覆盖区间,并将这些区间进行合并。
下面是wa1代码↓
import java.util.*;
public class Main
{
static int n,len;
static int N=(int)1e5+10;
static int[] L=new int[N],S=new int[N],P=new int[N];
static long[] b=new long[N];
public static boolean ck(int mid)
{
for(int i=1;i<=len;i++) b[i]=P[i]-P[i-1];
for(int i=1;i<=n;i++)
{
int ll=Math.max(1,L[i]-(mid-S[i]));
int rr=Math.min(len,L[i]+(mid-S[i]));
b[ll]+=1;
b[rr+1]-=1;
}
for(int i=1;i<=len;i++)
{
b[i]+=b[i-1];
if(b[i]<1) return false;
}
return true;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
len=sc.nextInt();
for(int i=1;i<=n;i++)
{
L[i]=sc.nextInt();
S[i]=sc.nextInt();
}
int l=1,r=(int)1e5;
while(l<r)
{
int mid=l+r>>1;
if(ck(mid)) r=mid;
else l=mid+1;
}
System.out.print(r);
}
}
正确代码
import java.util.*;
public class Main
{
static int n,len;
static int N=(int)1e5+10;
static int[] L=new int[N],S=new int[N];
public static boolean ck(int mid)
{
List<PII> p=new ArrayList<>();
for(int i=1;i<=n;i++)
if(mid>=S[i])
{
int t=mid-S[i]; //时间差
int st=Math.max(1,L[i]-t);
int ed=Math.min(len,L[i]+t);
p.add(new PII(st,ed));
}
Collections.sort(p); //按左边界值从小到大排好
int st=-1,ed=-1;
for(PII t:p)
{
if(t.x<=ed+1) //如果当前左边界≤正在维护的左边界-1
ed=Math.max(ed,t.y); //区间合并
else{
st=t.x;
ed=t.y;
}
}
return st==1&&ed==len;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
len=sc.nextInt();
for(int i=1;i<=n;i++)
{
L[i]=sc.nextInt();
S[i]=sc.nextInt();
//System.out.println(L[i]+" "+S[i]);
}
int l=1,r=(int)2e9; //最晚在10^9时刻打开第一个阀门,经过2*10^9才灌满
while(l<r)
{
int mid=(int)((long)l+r>>1);
if(ck(mid)) r=mid;
else l=mid+1;
}
System.out.print(l);
}
}
class PII implements Comparable<PII> //按第一个值从小到大排序
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII t)
{
return this.x-t.x;
}
}
4956. 冶炼金属 - 二分最大最小值
思路:
刚开始思路就是二分,但是纵观整个区间,V值并没有单调性(只有中间一段符合要求),所以我刚开始写了一个半二分,即前段暴力枚举后段二分,然后就tle了。
正解即分别二分最大最小值,利用了二分模板特性。
7/10 - 半二分
import java.util.*;
import java.io.*;
import java.math.*;
public class Main
{
static int n;
static int N=(int)1e4+10;
static PII[] w=new PII[N];
public static boolean ck(int mid)
{
for(int i=0;i<n;i++)
{
if((int)(Math.floor(w[i].x/mid))!=w[i].y) return false;
}
return true;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
int maxx=0;
for(int i=0;i<n;i++)
{
int a=sc.nextInt();
int b=sc.nextInt();
maxx=Math.max(maxx,a);
w[i]=new PII(a,b);
}
int ll=0;
for(int i=1;i<maxx;i++)
{
if(ck(i)) {
ll=i;break;
}
}
int l=ll,r=(int)maxx;
while(l<r)
{
int mid=(int)((long)l+r>>1);
if(!ck(mid)) r=mid;
else l=mid+1;
}
System.out.print(ll+" "+(r-1));
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
二分最小最大值 ac
import java.util.*;
import java.io.*;
import java.math.*;
public class Main
{
static int n;
static int N=(int)1e4+10;
static PII[] w=new PII[N];
public static boolean ck1(int mid)
{
for(int i=0;i<n;i++)
if((int)(Math.floor(w[i].x/mid))>w[i].y)
return false;
return true;
}
public static boolean ck2(int mid)
{
for(int i=0;i<n;i++)
if((int)(Math.floor(w[i].x/mid))<w[i].y)
return false;
return true;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
int maxx=0;
for(int i=0;i<n;i++)
{
int a=sc.nextInt();
int b=sc.nextInt();
maxx=Math.max(maxx,a);
w[i]=new PII(a,b);
}
int l=1,r=maxx;
while(l<r)
{
int mid=l+r>>1;
if(ck1(mid)) r=mid;
else l=mid+1;
}
System.out.print(l+" ");
r=maxx;
while(l<r)
{
int mid=l+r+1>>1;
if(ck2(mid)) l=mid;
else r=mid-1;
}
System.out.print(r);
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
789. 数的范围 - 二分最大最小值
二分模板题
import java.util.*;
import java.io.*;
import java.math.*;
public class Main
{
static int n,t;
static int N=(int)1e5+10;
static int[] a=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
t=sc.nextInt();
for(int i=0;i<n;i++) a[i]=sc.nextInt();
int ll=0,rr=0;
while(t-->0)
{
int x=sc.nextInt();
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
ll=l;
r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
rr=l;
if(a[l]!=x) {ll=-1;rr=-1;}
System.out.println(ll+" "+rr);
}
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
1236. 递增三元组 - 二分
思路:
给abc三个数组排序,枚举b数组,二分找a数组中最后一个小于b[i]的位置,再二分找c数组中第一个大于b[i]的位置,下面以枚举b[0]=5为例:
1 4 5
5 5 9
4 6 7
则b[0]=5满足条件的三元组个数为2×2=4个,方法即二分模板
import java.util.*;
public class Main{
static int n;
static int N=(int)1e5+10;
static int[] a=new int[N],b=new int[N],c=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
for(int i=1;i<=n;i++)
a[i]=sc.nextInt();
for(int i=1;i<=n;i++)
b[i]=sc.nextInt();
for(int i=1;i<=n;i++)
c[i]=sc.nextInt();
Arrays.sort(a,1,n+1);
Arrays.sort(b,1,n+1);
Arrays.sort(c,1,n+1);
long res=0;
int l,r;
for(int i=1;i<=n;i++)
{
l=0;r=n;
long s=1;
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<b[i]) l=mid;
else r=mid-1;
}
if(l==0) continue; //如果没找到比b[i]小的
s*=l;
l=1;r=n+1;
while(l<r)
{
int mid=l+r>>1;
if(c[mid]>b[i]) r=mid;
else l=mid+1;
}
if(r==n+1) continue; //如果没找到比b[i]大的
s*=(n-r+1);
res+=s;
}
System.out.print(res);
}
}
P3 - 前缀和
★一维前缀和模板
a数组下标从1开始,S[i] = S[i-1] + a[i]
则 [ Al,Ar ]段的和 = s[r] - s[l-1]
for(int i=1;i<=n;i++)
{
a[i]=sc.nextInt();
s[i]=s[i-1]+a[i];
}
[Al,Ar]的和 = s[r]-s[l-1]
★二维前缀和模板
static int N=1010;
static int[][] a=new int[N][N],s=new int[N][N];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
a[i][j]=sc.nextInt();
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
while(q-->0)
{
int x1=sc.nextInt(),y1=sc.nextInt(),x2=sc.nextInt(),y2=sc.nextInt();
int res=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
System.out.println(res);
}
1230. K倍区间 - 一维前缀和+数学
思路:
题目要求所有【连续子序列之和能整除k】的区间个数,则求和易想到前缀和,根据题目条件即满足(s[r]-s[l-1])%k==0,变换等式即s[r]%k==s[l-1]%k,所以若s[i]%k的值与前面s[i-t]%k的值相同,说明[i-t,i]这一段之和可以整除k,即为一个k倍区间。
最后要加上cnt[0],因为前面循环只考虑了两个取余相等的情况,单个s[i]%k=0的情况也需要考虑进去。
import java.util.*;
import java.io.*;
import java.math.*;
public class Main
{
static int n,k;
static long res;
static int N=(int)1e5+10;
static long[] s=new long[N];
static long[] cnt=new long[N]; //记录s[i]%k值的个数
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
k=sc.nextInt();
for(int i=1;i<=n;i++)
{
s[i]=sc.nextInt();
s[i]+=s[i-1];
}
for(int i=1;i<=n;i++)
{
res+=cnt[(int)(s[i]%k)]; //若s[r]%k的值和之前的s[i]%k相等,说明[i,r]是一个k倍区间
cnt[(int)(s[i]%k)]++; //记录当前s[i]%k的值
}
System.out.print(res+cnt[0]); //最后要加上单个余数为0的情况
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
4405. 统计子矩阵 - 二维前缀和
思路:
题目要求找该矩阵所有子矩阵和小于k的个数,则矩阵求和易想到二维前缀和模板。但是数据范围长宽都为500,4个for循环会TLE。因此我们限定上下界,由于矩阵数均为正数,因此区域越小,和越小,所以我们枚举右边界,移动左边界,直到区域之和小于k,则满足条件的子矩阵个数即为r-l+1。
import java.util.*;
public class Main{
static int N=510;
static int n,m,k;
static int[][] s=new int[N][N],a=new int[N][N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
int k=sc.nextInt();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
a[i][j]=sc.nextInt();
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
long res=0;
for(int u=1;u<=n;u++)
for(int d=u;d<=n;d++) //固定上下界
for(int l=1,r=1;r<=m;r++)
{
while(l<=r&&s[d][r]-s[u-1][r]-s[d][l-1]+s[u-1][l-1]>k) l++;
//因为矩阵都为正数,则缩小范围,具有单调性
if(l<=r) res+=r-l+1;
}
System.out.print(res);
}
}
99. 激光炸弹 - 二维前缀和
思路:
由于题目目标位置是以坐标呈现(x,y),而爆炸区域却是方块,为了方便,我们可以设目标位置也是单独的一个小方块
题目要求一颗炸弹能炸掉目标的最大价值,则我们可以先记录每一块的价值,然后用二维前缀和预处理,再遍历所有大小为R×R区域,取最大价值
import java.util.*;
public class Main{
static int N=5010;
static int n,r;
static int[][] s=new int[N][N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
r=sc.nextInt();
r=Math.min(r,5001);
for(int i=0;i<n;i++)
{
int x=sc.nextInt();
int y=sc.nextInt();
int w=sc.nextInt();
s[++x][++y]+=w; //前缀和下标从1开始,所以x和y都要+1
}
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
int res=0;
for(int i=r;i<=5001;i++)
for(int j=r;j<=5001;j++)
res=Math.max(res,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);
System.out.print(res);
}
}
P4 - 差分
★一维差分模板
给a数组 [l,r] 区间的每个数+c,只需要给其差分数组b做如下操作即可
b[l]+=c; b[r+1]-=c;
构造差分数组
![]()
for(int i=1;i<=n;i++) { a[i]=sc.nextInt(); b[i]=a[i]-a[i-1]; //构造差分数组 }
差分数组进行
![]()
操作
while(k-->0) { b[l]+=c; b[r+1]-=c; }
最后求差分数组b的前缀和,即为原数组在【l,r】段+c的数组
for(int i=1;i<=n;i++) { a[i]=a[i-1]+b[i]; //b的前缀和是a System.out.print(a[i]+" "); }
★二维差分模板
初始化
static int N=1010; static int[][] a=new int[N][N],b=new int[N][N]; public static void work(int x1,int y1,int x2,int y2,int c) { b[x1][y1]+=c; b[x2+1][y1]-=c; b[x1][y2+1]-=c; b[x2+1][y2+1]+=c; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { a[i][j]=sc.nextInt(); work(i,j,i,j,a[i][j]); }
求前缀和
work(x1,y1,x2,y2,c); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1]; System.out.print(b[i][j]+" "); } System.out.println(); }
5396.棋盘 - 二维差分
思路:
题目求二维区域翻转t次黑白子情况,我们只需要记录每个棋子的翻动次数即可,因为初始是白棋全为0,则结束所有翻动后,若该位置翻动次数是偶数,则说明该位置为白棋,否则为黑棋。二维区域加次数,易想到二维差分模板。
import java.util.*;
public class Main{
static int N=2010;
static int[][] a=new int[N][N],b=new int[N][N];
static int n,t;
public static void work(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1]+=c;
b[x1][y2+1]-=c;
b[x2+1][y1]-=c;
b[x2+1][y2+1]+=c;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
t=sc.nextInt();
while(t-->0)
{
int x1=sc.nextInt(),y1=sc.nextInt(),x2=sc.nextInt(),y2=sc.nextInt();
work(x1,y1,x2,y2,1);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
System.out.print(b[i][j]%2);
}
System.out.println();
}
}
}
4655. 重新排序 - 差分 + 排序
思路:
题目要求重新排列后区间累加最大和,对于样例
5 1 2 3 4 5 2 1 3 2 5
我们发现未排序前区间累加和是
1 2 3
2 3 4 5
而排列后最大累加和是
1 4 5
4 5 2 3
不难发现,需要累加和最大,即让重叠区域放大的数,思路有了,如何实现呢?
我们可以用差分记录重叠区域个数x,然后通过从小到大排序让最大的x个数乘2即可
——————————————————————————————————————
上述样例差分数组b[i]和原数组a[i]
1 2 2 1 1
1 2 3 4 5
未排序区间累加和为:s1=b[i]*a[i]=1*1+2*2+2*3+1*4+1*5=20
差分数组和原数组排列后
1 1 1 2 2
1 2 3 4 5
排序后区间累加和为:s2=b[i]*a[i]=1*1+1*2+1*3+2*4+2*5=24
import java.util.*;
public class Main{
static int N=(int)1e5+10;
static int n,m;
static int[] a=new int[N],s=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
for(int i=1;i<=n;i++) a[i]=sc.nextInt();
m=sc.nextInt();
while(m-->0)
{
int l=sc.nextInt(),r=sc.nextInt();
s[l]++;
s[r+1]--;
}
long s1=0,s2=0;
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int i=1;i<=n;i++) s1+=(long)s[i]*a[i];
Arrays.sort(s,1,n+1);
Arrays.sort(a,1,n+1);
for(int i=1;i<=n;i++) s2+=(long)s[i]*a[i];
System.out.print(s2-s1);
}
}
P5 - 双指针
799. 最长连续不重复子序列 - 滑动窗口
思路:
本题我们可以假设一个框框,我们枚举右边界,如果框框内出现重复数字,则缩小左边界直至框框内没有重复数字,滑动窗口用双指针来实现。
1 2 2 3 5 (发现框框内有重复!左边界缩小至框框内无重复)
1 2 2 3 5
1 2 2 3 5
import java.util.*;
public class Main
{
static int N=(int)1e5+10;
static int[] a=new int[N],cnt=new int[N];
static int n;
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
for(int i=0;i<n;i++) a[i]=sc.nextInt();
int maxx=0;
int l=0,r=0;
for(r=0;r<n;r++)
{
cnt[a[r]]++;
while(l<r&&cnt[a[r]]>1) cnt[a[l++]]--;
maxx=Math.max(maxx,r-l+1);
}
System.out.print(maxx);
}
}
800. 数组元素的目标和 - 二分 / 双指针
思路:
题目要求两个有序数组a[i]+b[j]=x,输出下标(i,j)
第一次一眼二分,写完之后捏了个数据发现不对呀,好像二分只能输出一个答案,于是删掉重写了个双指针,实现了输出所有答案——但是题目写着“保证唯一解”,然后就TLE了
二分和双指针代码都贴一下
双指针(12 / 16)
import java.util.*;
public class Main{
static int n,m,x;
static int N=(int)1e5+10;
static int[] a=new int [N],b=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
x=sc.nextInt();
for(int i=0;i<n;i++)
{
a[i]=sc.nextInt();
a[i]=x-a[i];
}
for(int i=0;i<m;i++) b[i]=sc.nextInt();
for(int i=0,j=0;i<n;i++)
{
while(b[j]<a[i]&&j<m) j++;
while(b[j++]==a[i]&&j<m) System.out.println(i+" "+(j-1));
j=0;
}
}
}
二分ac
import java.util.*;
public class Main{
static int n,m,x;
static int N=(int)1e5+10;
static int[] a=new int [N],b=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
x=sc.nextInt();
for(int i=0;i<n;i++)
{
a[i]=sc.nextInt();
a[i]=x-a[i];
}
for(int i=0;i<m;i++) b[i]=sc.nextInt();
for(int i=0;i<n;i++)
{
int l=0,r=m-1;
while(l<r)
{
int mid=l+r>>1;
if(b[mid]>=a[i]) r=mid;
else l=mid+1;
}
if(b[l]==a[i])
{
System.out.print(i+" "+l);
break;
}
}
}
}
2816. 判断子序列 - 双指针
思路:
st指针指向a序列的元素,直接在b序列里找a对应的元素,如果找到了st++,最后看st有没有把a序列全部指一遍
import java.util.*;
class Main
{
static int N=(int)1e5+10;
static int n,m;
static int[] a=new int[N],b=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
for(int i=0;i<n;i++) a[i]=sc.nextInt();
for(int j=0;j<m;j++) b[j]=sc.nextInt();
int st=0;
for(int i=0;i<m;i++)
{
if(a[st]==b[i]) st++;
if(st>=n) break;
}
if(st==n) System.out.print("Yes");
else System.out.print("No");
}
}
1238. 日志统计 - 滑动窗口
思路:
题目表明在[T,T+D)时间段内点赞不少于k个的帖子为热帖,要求输出热帖id
我们可以将所有记录用二维数组存储,按时间从小到大排序
维护滑动窗口,使窗口内的时间段不超过时限(即框起来的时间为[T,T+D)),统计窗口内的ID,如果超过时限,则缩小左边界,使窗口内时限合法
import java.util.*;
class Main{
static int n,d,k;
static int N=(int)1e5+10;
static int[] cnt=new int[N],st=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
d=sc.nextInt();
k=sc.nextInt();
int[][] a=new int[n][2];
for(int i=0;i<n;i++)
{
a[i][0]=sc.nextInt(); //时刻
a[i][1]=sc.nextInt(); //id号
}
Arrays.sort(a,(o1,o2)->{return o1[0]-o2[0];}); //按时间从小到大排序
for(int l=0,r=0;r<n;r++)
{
int id=a[r][1];
cnt[id]++;
while(a[r][0]-a[l][0]>=d)
{
cnt[a[l][1]]--;
l++;
}
if(cnt[id]>=k) st[id]=1;
}
for(int i=0;i<=100000;i++) if(st[i]==1) System.out.println(i);
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
P6 - BFS/DFS
1233. 全球变暖 - BFS
思路:
BFS模板题,从某块陆地开始bfs(向四周扩展+标记),如果发现这个岛屿只要存在一块陆地四周无海洋(#四周无.),则这就是一块未沉的岛屿。题目要求计算有多少岛屿沉没,则我们可以用【岛屿总数-未沉没岛屿数】求的。
关于岛屿总数怎么求,因为bfs从某块陆地开始扩展,该陆地的所有岛屿都会被bfs蔓延标记,因此主函数中循环里只有存在未标记的陆地块,岛屿数++。换句话说,对某块陆地进行bfs后,这个陆地所在岛屿上所有陆地都会被标记,所以若此后出现未标记的陆地,肯定是新的一座岛屿。
package abcd;
import java.util.*;
public class lanqiaoo {
static int N=(int)1010;
static int n,res,island;
static char[][] g=new char[N][N];
static int[][] st=new int[N][N];
static int[] dx={1,-1,0,0},dy= {0,0,1,-1};
public static void bfs(int x,int y)
{
Queue<PII> q=new LinkedList<>();
q.offer(new PII(x,y));
st[x][y]=1;
boolean f=false;
while(!q.isEmpty())
{
int cnt=0; //统计(x,y)这块地四周土地的个数
PII t=q.poll();
for(int i=0;i<4;i++)
{
int nx=t.x+dx[i],ny=t.y+dy[i];
if(nx<0||nx>=n||ny<0||ny>=n||g[nx][ny]=='.') continue;
cnt++;
if(st[nx][ny]==0)
{
q.offer(new PII(nx,ny));
st[nx][ny]=1;
}
}
if(cnt==4&&f==false) //只统计第一次,因为一个岛上可能有多个陆地
{
res++;
f=true;
}
}
}
public static void main(String[] args)
{
Scanner sc=new Scanner (System.in);
n=sc.nextInt();
for(int i=0;i<n;i++)
{
String s=sc.next();
g[i]=s.toCharArray();
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
char x=g[i][j];
if(x=='#'&&st[i][j]==0)
{
bfs(i,j);
island++;
}
}
}
System.out.print(island-res); //沉没的岛屿数=总岛屿数-未沉没的岛屿数
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x, o.x);
}
}
P7 - 区间合并
★区间合并模板
step1:将每个区间按左端点从小到大进行排序
step2:如图所示,可分3种情况
- 情况一:当前区间完全被上一区间覆盖,直接跳过
- 情况二:将当前区间的右端点更新为上一区间的右端点,达到区间延长的效果
- 情况三:当前区间的左端点严格大于上一区间的右端点,则表示该区间不能合并,更新区间且count++
803. 区间合并
import java.util.*;
class Main{
static int n;
static int N=(int)1e5+10;
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
List<PII> a=new ArrayList<>();
n=sc.nextInt();
for(int i=0;i<n;i++)
{
int l=sc.nextInt(),r=sc.nextInt();
a.add(new PII(l,r));
}
Collections.sort(a);
int res=0;
int st=Integer.MIN_VALUE,ed=Integer.MIN_VALUE;
for(int i=0;i<a.size();i++)
{
int l=a.get(i).x,r=a.get(i).y;
if(l>ed) //两区间没重合
{
res++;
st=l;
ed=r;
}
else //区间重合,ed取最大,达到延长区间效果
{
ed=Math.max(ed,r);
}
}
System.out.println(res);
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
422. 校门外的树 - 模拟+区间合并
import java.util.*;
class Main{
static int n,len;
static int N=(int)1e4+10;
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
List<PII> a=new ArrayList<>();
len=sc.nextInt();
n=sc.nextInt();
for(int i=0;i<n;i++)
{
int l=sc.nextInt();
int r=sc.nextInt();
a.add(new PII(l,r));
}
Collections.sort(a);
int res=0;
int st=a.get(0).x,ed=a.get(0).y;
for(int i=1;i<a.size();i++)
{
int l=a.get(i).x,r=a.get(i).y;
if(l>ed) //两区间没重合
{
res+=ed-st+1;
st=l;
ed=r;
}
else //区间重合,ed取最大,达到延长区间效果
{
ed=Math.max(ed,r);
}
}
res+=ed-st+1;
System.out.println(len+1-res);
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x,o.x);
}
}
P8 - 日期问题
2867. 回文日期 - 回文串+日期模拟
思路:
简单粗暴,ck1判断回文串,ck2判断ABABBABA型回文串,year判断是否为正确日期
注意:回文串和ABABBABA型串可能是同一天,第一行输出回文串,第二行输出ABABBABA型串,还要注意日期合法(自己做上面的坑全踩了)
import java.util.*;
public class Main {
static int N=(int)1e5+10;
static int n,m;
public static boolean ck1(String s)
{
for(int i=0;i<s.length()/2;i++)
{
int j=s.length()-1-i;
if(s.charAt(i)!=s.charAt(j)) return false;
}
return true;
}
public static boolean ck2(String s)
{
String a=s.substring(0,4),b=s.substring(4,8);
StringBuffer bb=new StringBuffer(b);
b=String.valueOf(bb.reverse());
char x1=a.charAt(0),x2=a.charAt(1),x3=a.charAt(2),x4=a.charAt(3);
if(b.equals(a)&&x1!=x2&&x3!=x4&&x1==x3&&x2==x4) return true;
return false;
}
public static boolean run(int y)
{
if(y%400==0||(y%4==0)&&(y%100!=0)) return true;
return false;
}
public static boolean year(String s)
{
String y=s.substring(0,4),m=s.substring(4,6),d=s.substring(6);
int yy=Integer.parseInt(y),mm=Integer.parseInt(m),dd=Integer.parseInt(d);
if(mm==0||mm>12||dd==0||dd>31) return false;
if(mm==2)
{
if(run(yy)&&dd>29) return false;
else if(!run(yy)&&dd>28) return false;
}
if((mm==4||mm==6||mm==9||mm==11)&&dd>30) return false;
return true;
}
public static void main(String[] args)
{
Scanner sc=new Scanner (System.in);
n=sc.nextInt();
int st1=0,st2=0;
String s=String.valueOf(n);
for(int i=n+1;i<=99999999;i++)
{
if(st1==1&&st2==1) break;
String t=String.valueOf(i);
if(!year(t)) continue;
if(ck1(t)&&st1==0)
{
st1++;
System.out.println(t);
if(ck2(t))
{
st2++;
System.out.print(t);
break;
}
}
if(ck2(t)&&st2==0)
{
st2++;
System.out.println(t);
}
}
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x,int y)
{
this.x=x;
this.y=y;
}
public int compareTo(PII o)
{
return Integer.compare(x, o.x);
}
}