基础算法

文章详细介绍了二分查找的基础查询、区间查找以及它们在查找区间和插入位置中的应用。同时,涵盖了双指针在解决最长连续不重复子序列、两个有序数组目标和、判断子序列等问题上的策略。还讨论了排序算法,如冒泡排序、快速排序、归并排序和堆排序,以及如何找到数组中小于当前元素的个数、有效山脉数组、独一无二的出现次数等数组操作问题。
摘要由CSDN通过智能技术生成

二分查找

基础查询

在这里插入图片描述

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");
	}
}

接雨水

在这里插入图片描述

  1. 每一列雨水的高度=Math.min(该列左边最大高度,该列右边最大高度)-当前高度
  2. 从左往右遍历,每一列左边最大高度=Math.max(前一列左边最大高度,当前高度)
  3. 从右往左遍历,每一列右边最大高度=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;
    }
}

有效的山脉数组

在这里插入图片描述

  1. 本题是判断数组是否具有严格单调性
  2. 左右指针分别指向首尾,只有在具有严格单调性的时候才往中间靠拢
  3. 如果左右指针最终相遇,且不是起点,则具有严格单调性
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;
    }
}

独一无二的出现次数

在这里插入图片描述

  1. 使用cnt数组记录每个数出现的次数,但是由于可能会有负数,但是由于数据范围是[-1000,1000],因此可以将原数据加上1000,保证记录的下标不是负数
  2. 额外使用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;
        }
    }
}

旋转数组

在这里插入图片描述

  1. 右旋转:先整体翻转,再翻转前k个,最后翻转后n-k个
  2. 左旋转:先翻转前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;
    }
}

有效数组的平方和

在这里插入图片描述

双指针:

  1. i指向头部,j指向尾部
  2. 新数组从后往前赋值,比较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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值