文章目录
二分查找
基础查询
class Solution {
public int search(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>target){
right=mid-1;
}else if(nums[mid]<target){
left=mid+1;
}else{
return mid;
}
}
return -1;
}
}
区间查找 |
左边界 右边界
mid=l+r >>1; mid=l+r+1 >>1;
if(a[mid]>=x)r=mid; if(a[mid]<=x)l=mid;
else l=mid+1; else r=mid-1;
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int q=sc.nextInt();
int a[]=new int [n];
for(int i=0;i<n;i++)
a[i]=sc.nextInt();
while(q-->0){ //q次询问,注意是>0
int x=sc.nextInt(); //询问的数
check(a,0,n-1,x,n); //查询a数组[0,n-1]区间里x出现区间
}
}
public static void check(int a[],int l,int r,int x,int n){
while(l<r){
int mid=l + r >>1;
if(a[mid]>=x)r=mid;
else l=mid+1;
}
if(a[l]!=x){
System.out.println("-1 -1");
}else{
System.out.print(l+" "); //左边界
l=0;r=n-1;
while(l<r){
int mid=(l + r + 1)/2;
if(a[mid]<=x)l=mid;
else r=mid-1;
}
System.out.println(l); //右边界
}
}
}
区间查找 ||
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftBorder=getLeftBorder(nums,target);
int rightBorder=getRightBorder(nums,target);
//所查找的元素不存在
if(leftBorder==-2||rightBorder==-2)return new int[]{-1,-1};
//存在左右边界
if(rightBorder-leftBorder>1)return new int[]{leftBorder+1,rightBorder-1};
//不在范围内
return new int[]{-1,-1};
}
//左端点,但是不包括target
public int getLeftBorder(int[]nums,int target){
int left=0;
int right=nums.length-1;
int leftBorder=-2; //用于判断是否存在查找元素
while(left <= right){
int mid=left+(right-left)/2;
if(nums[mid]>=target){ //相等时更新右边界,才能得到左边界
right=mid-1;
leftBorder=right;
}else{
left=mid+1;
}
}
return leftBorder;
}
//右端点,但是不包括target
public int getRightBorder(int[]nums,int target){
int left=0;
int right=nums.length-1;
int rightBorder=-2; //用于判断是否存在查找元素
while(left <= right){
int mid=left+(right-left)/2;
if(nums[mid]>target){
right=mid-1;
}else{ //相等时更新左边界,才能得到右边界
left=mid+1;
rightBorder=left;
}
}
return rightBorder;
}
}
插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>target){
right=mid-1;
}else if(nums[mid]<target){
left=mid+1;
}else {
return mid; //相等就直接返回下标
}
}
//其余情况:比所有数大,比所有数小,能插入中间
return right+1;
}
}
双指针
最长连续不重复子序列的长度
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int []a=new int [100010];
int []s=new int [100010];
int n=sc.nextInt();
int res=0;
for(int i=0;i<n;i++)a[i]=sc.nextInt();
for(int i=0,j=0;i<n;i++){
s[a[i]]++;
while(j<i&&s[a[i]]>1){
s[a[j]]--; //将i遍历过的值出现次数减为0
j++; //将j移动到i的不重复位置
}
res=Math.max(res,i-j+1);
}
System.out.println(res);
}
}
两个有序数组的目标和
import java.io.*;
public class twosum{
static int N=100010; //静态常量的声明
static int a[]=new int [N];
static int b[]=new int [N];
public static void main(String[] args)throws Exception { //抛出异常
//大数据的读取流
BufferedReader sc=new BufferedReader(new InputStreamReader(System.in));
//按照字符串来读取一行
String[]s=sc.readLine().split(" ");
int n=Integer.parseInt(s[0]); //第一个数组的长度
int m=Integer.parseInt(s[1]); //第二个数组的长度
int sum=Integer.parseInt(s[2]); //目标和
String[]A=sc.readLine().split(" "); //整行读取数组
for(int i=0;i<n;i++)a[i]=Integer.parseInt(A[i]); //转换字符串为整数
String[]B=sc.readLine().split(" ");
for(int j=0;j<m;j++)b[j]=Integer.parseInt(B[j]);
int i=0;
int j=m-1;
while(i<n&&j>0){ //a数组从0开始,b数组从末尾开始
if(a[i]+b[j]>sum){ //b回退
j--;
}else if(a[i]+b[j]<sum){ //a前进
i++;
}else{
System.out.println(i+" "+j); //输出唯一解
break; //跳出防止死循环
}
}
}
}
判断子序列
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
int a[]=new int[n];
int b[]=new int[m];
for(int i=0;i<n;i++)a[i]=sc.nextInt();
for(int j=0;j<m;j++)b[j]=sc.nextInt();
int i=0,j=0;
while(i<n&&j<m){
if(a[i]==b[j])i++;//相等才移动
j++;//b一直移动
}
if(i==n)System.out.println("Yes");
else System.out.println("No");
}
}
接雨水
- 每一列雨水的高度=Math.min(该列左边最大高度,该列右边最大高度)-当前高度
- 从左往右遍历,每一列左边最大高度=Math.max(前一列左边最大高度,当前高度)
- 从右往左遍历,每一列右边最大高度=Math.max(前一列右边最大高度,当前高度
class Solution {
public int trap(int[] height) {
int len=height.length;
if(len<=2)return 0;
int[] maxLeft=new int[len];
int[] maxRight=new int[len];
maxLeft[0]=height[0];
//i列左边最大的高度=Max(前一列左边最大高度,当前高度)
for(int i=1;i<len;i++){
maxLeft[i]=Math.max(maxLeft[i-1],height[i]);
}
maxRight[len-1]=height[len-1];
//i列右边最大的高度=Max(前一列右边最大高度,当前高度)
for(int j=len-2;j>=0;j--){
maxRight[j]=Math.max(maxRight[j+1],height[j]);
}
int h=0,sum=0;
for(int i=0;i<len;i++){
h=Math.min(maxLeft[i],maxRight[i])-height[i];
if(h>0)sum+=h;
}
return sum;
}
}
长按键入
1. i,j分别从头开始遍历name,typed
2. 如果name[i]=typed[j],则i++,j++
3. 否则检验是否是第一个不相等,j==0时则为false
4. 否则要跳过typed的重复项,使用while去重
5. 去重后再比较name[i]和typed[j]
6. 最后如果i没有到达name终点,则匹配不完,返回false
7. 最后如果j没有到达终点,则使用while去重判断是否仅剩下同一个字母
class Solution {
public boolean isLongPressedName(String name, String typed) {
int i=0,j=0;
while(i<name.length() && j<typed.length()){
if(name.charAt(i)==typed.charAt(j)){
i++;j++;
}else{
if(j==0)return false;
else{
while(j<typed.length()-1 && typed.charAt(j)==typed.charAt(j-1))j++; //注意这里j<len-1!
if(name.charAt(i)==typed.charAt(j)){
i++;j++;
}
else return false;
}
}
}
if(i<name.length())return false;
while(j<typed.length()){
if(typed.charAt(j)==typed.charAt(j-1))j++;
else return false;
}
return true;
}
}
比较含退格的字符串
1. i,j指针分别指向s,t的末尾
2. 遇到 # 就记录skip ++,进入下一次循环,如果skip>0则复位后继续 i - -,这样就能实现退格的效果
3. 如果此时skip已经为0,则此时取出s[i]与t[j]比较,如果不相等直接return false;
4. 如果i,j其中有一个到达终点i==-1 || j==-1,则判断i,j是否同时到达终点
class Solution {
public boolean backspaceCompare(String s, String t) {
int skipS=0,skipT=0;
int i=s.length()-1,j=t.length()-1;
while(true){
while(i>=0){
if(s.charAt(i)=='#')skipS++;
else{
if(skipS>0)skipS--; //实现退格的效果
else break; //跳出循环进行比较
}
i--;
}
while(j>=0){
if(t.charAt(j)=='#')skipT++;
else{
if(skipT>0)skipT--;
else break;
}
j--;
}
if(i<0||j<0)break; //其中有一个指针已经到达终点
if(s.charAt(i)!=t.charAt(j))return false;
i--;j--; //相等则同时向前移动,继续比较
}
if(i==-1&&j==-1)return true; //两个指针同时到达终点
else return false;
}
}
排序
冒泡排序
public static void bublle_sort(int q[],int n){
for(int i=0;i<n-1;i++)
for(int j=0;j<n-1-i;j++)
if(q[j]>q[j+1]){
int t=q[j];
q[j]=q[j+1];
q[j+1]=t;
}
}
快速排序
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int q[]=new int[n];
for(int i=0;i<n;i++)q[i]=sc.nextInt();
q_sort(q,0,n-1);
for(int i=0;i<n;i++)System.out.print(q[i]+" ");
}
public static void q_sort(int q[],int l,int r){
if(l>=r)return;
int i=l-1,j=r+1,mid=q[l+r >>1];
while(i<j){
while(q[++i]<mid);
while(q[--j]>mid);
if(i<j){
int temp=q[i];
q[i]=q[j];
q[j]=temp;
}
}
q_sort(q,l,j);
q_sort(q,j+1,r);
}
}
}
第k小的数
import java.util.*;
public class select_sort{
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int k=sc.nextInt();
int q[]=new int[n];
for(int i=0;i<q.length;i++)q[i]=sc.nextInt();
int res=select_sort(q,0,n-1,k);
System.out.print(q[res]);
}
public static int select_sort(int q[],int l,int r,int k){
if(l>=r)return l;
int i=l-1,j=r+1,mid=q[l + r >>1];
while(i<j){
while(q[++i]<mid);
while(q[--j]>mid);
if(i<j){
int temp=q[i];
q[i]=q[j];
q[j]=temp;
}
}
int dis=j-l+1;
if(k<=dis)return select_sort(q,l,j,k);
else return select_sort(q,j+1,r,k-dis); //k在后半部分的相对位置
}
}
归并排序
归并排序模板
public static void merge_sort(int arr[],int l,int r){
if(l==r)return; //递归结束条件
int mid=(l+r)>>1; //选取分界点
int i=l,j=mid+1,k=0; //分界后的两个起点
int temp[]=new int[r-l+1];
merge_sort(arr,l,mid);
merge_sort(arr,mid+1,r);
while(i<=mid&&j<=r){
if(arr[i]<=arr[j])temp[k++]=arr[i++];
else temp[k++]=arr[j++];
}
while(i<=mid)temp[k++]=arr[i++]; //处理后续
while(j<=r)temp[k++]=arr[j++];
for(i=l,j=0;i<=r;i++,j++)arr[i]=temp[j]; //交回
}
数列逆序对数量
import java.util.*;
import java.io.*;
public class Main{
public static void main(String[] args) {
Scanner sc=new Scanner(new InputStreamReader(System.in));
int n=sc.nextInt();
int []a=new int[n];
for(int i=0;i<n;i++)
a[i]=sc.nextInt();
System.out.println(merge_sort(a,0,n-1));
sc.close();
}
public static long merge_sort(int []a,int l,int r){
if(l==r)return 0; //逆序对数量为0时结束递归
int []t=new int [r-l+1];
int mid=l + r >>1;
long res=merge_sort(a,l,mid)+merge_sort(a,mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
t[k++]=a[i++];
}else{
res+=mid-i+1; //此时左边部分i之后的都大于右边的这个数,形成逆序对
t[k++]=a[j++];
}
}
while(i<=mid)t[k++]=a[i++];
while(j<=r)t[k++]=a[j++];
for(i=l,j=0;i<=r;i++,j++)a[i]=t[j];
return res;
}
}
堆排-前m小的数
每次形成小根堆,输出堆顶最小值,再用堆尾将其覆盖(相当于删除当前数组的首元素),重新形成小根堆,此时堆顶就是次小元素,将其输出,每一次形成堆都会是剩余的最小值
import java.util.Scanner;
public class Main{
static int N=100010;
static int []h=new int[N];
static int size;//堆的右边界
public static void down(int u){
int t=u;//t保存最小结点的下标
//左儿子存在且左儿子小于父节点,替换坐标
if(2*u<=size&&h[2*u]<h[t])t=2*u;
//右儿子存在且右儿子小于父节点,替换坐标
if(2*u+1<=size&&h[2*u+1]<h[t])t=2*u+1;
if(u!=t){ //最小结点不是t,则与最小值交换
swap(u,t);
down(t); //递归最小值t
}
}
public static void swap(int x,int y){
int temp=h[x];
h[x]=h[y];
h[y]=temp;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
size=n; //右边界
int m=sc.nextInt();
for(int i=1;i<=n;i++)h[i]=sc.nextInt(); //下标从1开始
for(int i=n/2;i>=1;i--)down(i); //形成小根堆
while(m-->0){ //先后输出m个最小值
System.out.print(h[1]+" "); //输出堆顶最小值
h[1]=h[size--]; //相当于覆盖并删除掉最小值
down(1);
}
}
}
数组
有多少个小于当前的数
1. 将数组由小到大排序,排序后下标就是比他小的数的个数
2. 排序后,用map来保存每个数与坐标的映射
3. 最后按照原数组的顺序输出其在排序中映射的坐标
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
HashMap<Integer,Integer>map=new HashMap<>();
int []res=Arrays.copyOf(nums,nums.length);
Arrays.sort(res);
for(int i=0;i<res.length;i++){
if(!map.containsKey(res[i])){ //重复元素不用更新映射了
map.put(res[i],i);
}
}
for(int i=0;i<nums.length;i++){
res[i]=map.get(nums[i]);
}
return res;
}
}
有效的山脉数组
- 本题是判断数组是否具有严格单调性
- 左右指针分别指向首尾,只有在具有严格单调性的时候才往中间靠拢
- 如果左右指针最终相遇,且不是起点,则具有严格单调性
class Solution {
public boolean validMountainArray(int[] arr) {
int len=arr.length;
int left=0,right=len-1;
//注意数组不要越界
while(left<len-1 && arr[left]<arr[left+1])left++;
while(right>0 && arr[right-1]>arr[right])right--;
if(left==right && left!=0 &&right!=len-1)return true;
return false;
}
}
独一无二的出现次数
- 使用cnt数组记录每个数出现的次数,但是由于可能会有负数,但是由于数据范围是[-1000,1000],因此可以将原数据加上1000,保证记录的下标不是负数
- 额外使用frecnt数组判断是否频率重复
class Solution {
public boolean uniqueOccurrences(int[] arr) {
int len=arr.length;
int[]cnt=new int[2002];
boolean[]frecnt=new boolean[1001]; //记录出现的频率是否重复
for(int i=0;i<len;i++){
cnt[arr[i]+1000]++;
}
for(int i=0;i<2000;i++){ //因为数据范围是[-1000,1000],因此可以遍历1~2000这些数
if(cnt[i]>0){ //出现次数大于0
if(frecnt[cnt[i]]==false){
frecnt[cnt[i]]=true;
}else{
return false;
}
}
}
return true;
}
}
移动零
1. 只有当数组元素不为0时,快慢指针才同时移动,完成赋值
2. 最后将慢指针后面的元素赋值为0即可
class Solution {
public void moveZeroes(int[] nums) {
int len=nums.length;
int fastIndex=0,slowIndex=0;
for(fastIndex=0;fastIndex<len;fastIndex++){
if(nums[fastIndex]!=0){
nums[slowIndex++]=nums[fastIndex];
}
}
for(int i=slowIndex;i<len;i++){
nums[i]=0;
}
}
}
旋转数组
- 右旋转:先整体翻转,再翻转前k个,最后翻转后n-k个
- 左旋转:先翻转前k个,后翻转n-k个,最后整体翻转
class Solution {
public void rotate(int[] nums, int k) {
int n=nums.length;
k=k%n;
reverse(nums,0,n-1);
reverse(nums,0,k-1);
reverse(nums,k,n-1);
}
public void reverse(int[]nums,int start,int end){
int temp=0;
for(int i=start,j=end;i<j;i++,j--){
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
}
寻找数组中心下标索引
1. 计算包括nums[i]在内的左半边总和
2. 计算包括nums[i]在内的右半边总和
3. 如果左半边总和==右半边总和,则返回true;
class Solution {
public int pivotIndex(int[] nums) {
int leftSum=0,rightSum=0,totalSum=0;
for(int i=0;i<nums.length;i++){
totalSum+=nums[i];
}
for(int i=0;i<nums.length;i++){
leftSum+=nums[i]; //左半边总和
rightSum=totalSum-leftSum+nums[i]; //右半边总和
if(leftSum==rightSum)return i;
}
return -1;
}
}
奇偶下标
将奇数和偶数按照从下标1,0开始,放置到res数组
class Solution {
public int[] sortArrayByParityII(int[] nums) {
int []res=new int[nums.length];
int evenIndex=0,oddIndex=1;
for(int i=0;i<nums.length;i++){
if(nums[i]%2==0){
res[evenIndex]=nums[i];
evenIndex+=2;
}else{
res[oddIndex]=nums[i];
oddIndex+=2;
}
}
return res;
}
}
移除元素
class Solution {
public int removeElement(int[] nums, int val) {
int slow=0,fast=0;
for(fast=0;fast<nums.length;fast++){
if(nums[fast]!=val){ //当不等于移除元素时,快指针赋值给慢指针
nums[slow++]=nums[fast];
}
}
return slow;
}
}
有效数组的平方和
双指针:
- i指向头部,j指向尾部
- 新数组从后往前赋值,比较nums[i]*nums[i]和nums[j]*nums[j],较大的赋值给res
class Solution {
public int[] sortedSquares(int[] nums) {
int len=nums.length;
int k=len-1;
int[]res=new int[len];
for(int i=0,j=len-1;i<=j;){
if(nums[i]*nums[i]<nums[j]*nums[j]){
res[k--]=nums[j]*nums[j];
j--;
}else{
res[k--]=nums[i]*nums[i];
i++;
}
}
return res;
}
}
长度最小的子数组长度
1.设置左边界和右边界
2.移动右边界进行求和,当和满足条件时,求长度并移动左边界
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int len=nums.length;
int left=0,sum=0,res=Integer.MAX_VALUE;
for(int right=0;right<len;right++){
sum+=nums[right];
while(sum>=target){ //求长度并移动窗口
res=Math.min(res,right-left+1);
sum-=nums[left++]; //移动起始位置
}
}
return res==Integer.MAX_VALUE?0:res;
}
}
螺旋矩阵
class Solution {
public int[][] generateMatrix(int n) {
int [][]matrix=new int[n][n];
int loop=n/2; //控制循环次数,例如n=3时,loop=3/2=1,循环一圈,中间单独考虑
int offset=1; //控制边界长度
int cnt=1; //填充的数
int startx=0,starty=0; //每一圈填充的开始下标
int mid=n/2; //n为奇数时,单独考虑正中间的数
while(loop-->0){
int i=startx,j=starty;
//上行从左到右
for(j=starty;j<n-offset;j++){
matrix[startx][j]=cnt++;
}
//右列从上到下
for(i=startx;i<n-offset;i++){
matrix[i][j]=cnt++;
}
//下行从右到左
for(;j>=offset;j--){
matrix[i][j]=cnt++;
}
//左列从下到上
for(;i>=offset;i--){
matrix[i][j]=cnt++;
}
//起始位置改变
startx++;
starty++;
//循环边界缩短
offset+=1;
}
//n为奇数时,单独考虑正中间的数
if(n%2!=0){
matrix[mid][mid]=cnt;
}
return matrix;
}
}