acwing(一)—基础算法总结

本博文主要是记录acwing算法基础课第一章基础算法和其模板的相关内容,记录下来供以后复习。


要求:

  • 把算法的思想搞懂。
  • 课后把代码模板背过,能够达到快速默写出来、调试通过就好了。
  • 每一个模板重复写几遍。

1、排序

1.1、 快速排序——分治

思路:

  • 确定分界点:q[i]、q[(i+r)/2]、q[r]
  • 调整区间:分界点左边是小于x的,分界点右边是大于x的。
  • 递归处理左右两段。

如何优雅的将数组一分为2

  • 左右指针分别交换来替换。
  • 分割点x选第一个数

注意点:

  • 注意边界问题。
  • 这个模板背就完事了

题目:
在这里插入图片描述

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        
        int n = in.nextInt();
        int[] arr = new int[n];
        for(int i = 0; i < n; i++){
            arr[i] = in.nextInt();
        }
        
        quickSort(arr, 0, n - 1);
        
        for(int i = 0; i < n; i++){
            System.out.print(arr[i] + " ");
        }
    }
    
    public static void quickSort(int[] arr, int l, int r){
        if(l>=r) return;
        int i = l - 1; 	//这里很容易出错,因为是先加和先减的,所以要在左右边边界分别加减1.
        int j = r + 1;
        int x = arr[l];
        
        while(i < j){
            do i++; while(arr[i]<x);
            do j--; while(arr[j]>x);
            if(i < j){
                int a = arr[i];
                arr[i] = arr[j];
                arr[j] = a;
            }
        }
        
        quickSort(arr, l, j);
        quickSort(arr, j + 1, r);
    }
}

1.2、 归并排序——分治

快排是以一个数来分,归并排序是以中间的一个数来分。

思想

  • 找分界点,mid = (l+r)/2

  • 递归排序

  • 归并——合二为一(重点)

    两个排好序的,顺序取最小值,合并为一个数组。

    image-20210627201003388
    题目:
    在这里插入图片描述
    解法:

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main{
    public static void main(String[] args) {
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        
        int n = in.nextInt();
        int[] arr = new int[n];
        for(int i = 0; i < n; i++){
            arr[i] = in.nextInt();
        }
        
        mergeSort(arr, 0, n-1);
        
        for(int i = 0; i < n; i++){
            System.out.print(arr[i] + " ");
        }
        
    }
    
    public static void mergeSort(int[] arr, int l, int r){
        if(l >= r) return;
        
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        
        int[] tmp = new int[r - l + 1];
        
        int k = 0, i = l, j = mid + 1;
        while( i <= mid && j <= r){
            if(arr[i] <= arr[j]) 
                tmp[k++] = arr[i++];
            else 
                tmp[k++] = arr[j++];
        }
        
        while(i <= mid) tmp[k++] = arr[i++];
        while(j <= r) tmp[k++] = arr[j++];
        
        for(i = l, j = 0; i <= r; i++, j++) 
            arr[i] = tmp[j];
    }
}

2、二分

2.2、 整数二分

模板:

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

有单调性一定可以二分,没有单调性也可能能够二分。二分的本质是:边界中一分为二,一个半边是满足这个性质,一个半边不满足。

为了更好的处理边界问题我们引入了模板,但是该如何选择用哪个模板呢?可以用下面这个图来理解。

在这里插入图片描述
显然,如果查找数字11,可能出现11的地方是一个区间,如果我们需要查找这个区间的左端点,如橙色箭头所示,当我们选取到Array[mid]的时候,11是会落在左区间的的,因此选用第一个模板区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用。当我们要查找区间的右端点的时候,如蓝色箭头所示,就会用到第二个模板。这里需要自己体会一下。

模板1与模板2的区别在于:模板2取mid的时候加上了1.

题目:

import java.util.Scanner;
import java.io.BufferedInputStream;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        int n = in.nextInt();
        int m = in.nextInt();
        int[] arr = new int[n];
        for(int i = 0; i < n; i++){
            arr[i] = in.nextInt();
        }
        while(m-->0){
            int x = in.nextInt();
            int l = 0, r = n - 1;
            while(l < r){
                int mid = l + ((r - l) >> 1);
                if(arr[mid]>=x) r = mid;//想想往左边区间找还是往右边区间找这个数
                else l = mid + 1;
            }
            if(arr[l] != x){
                System.out.println("-1 -1");
            }else{
                String ans = l + " ";
                l = 0;
                r = n - 1;
                while(l < r){
                    int mid = l + ((r - l + 1) >> 1);
                    if(arr[mid]<=x)l = mid;
                    else r = mid - 1;
                }
                System.out.println(ans + l);
            }
        }
    }
}

2.3、 小数二分(浮点数二分)

求根号x
在这里插入图片描述

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        double n = in.nextDouble();

        double l = 0, r = Math.abs(n);  // 考虑 n为负数的情况

        while (r - l > 1e-8) {  // // 精度比所求精度高 2位
            double mid = (l + r) / 2;
            if (mid * mid * mid >= Math.abs(n))   // 不需要考虑边界问题
                r = mid;
            else
                l = mid;
        }
        if (n >= 0)
            System.out.println(String.format("%.6f", l)); // 保留 6位小数
        else
            System.out.println("-" + String.format("%.6f", l));
    }
}

3、高精度

java和python同学没有必要学这个。先不学。

  • 两个大的整数相加
  • 两个大整数相减
  • 大整数*小整数
  • 大整数除整数

4、前缀和与差分

前缀和的作用是:能快速的算出数组的每一部分的连续和。

S0=0有两个优点:

  • 生成前缀和很方便
  • 计算i+~-j很方便。Si-Sj-1

模板:

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

其实是一个公式和一个思想,算法比较简单。

5、双指针算法

归并排序的时候需要将两个子序列合并的时候用的是双指针算法。
核心思想是:

  • 暴力写法是O(n^2)的。但是双指针优化到O(n),运用了某些性质
for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

image-20210818213201002

一个没有重复的连续子序列内部都是没有重复元素的,所以j指向的只能右移,所以只要枚举i就可以了。
数据很大的时候可以用hashmap来做。不开数组来判重的方法就是hashmap

import java.util.Scanner;
import java.util.*;
//为啥能够用maps来判断重复呢?i走过了一遍,
//知道重复了多少次,j是从左边走过来,直到新加进来的不重复就是
//原来的本身就不重复,但新进来的是不是重复呢?就看j的移动是不是能够让它降到1
//此时说明j走到了一个合适的位置。
public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] arr = new int[n];
        for(int i = 0; i < n; i++){
            arr[i] = in.nextInt();
        }
        int ans = 0;
        Map<Integer, Integer> maps = new HashMap<Integer, Integer>();
        for(int i = 0,j = 0; i < n; i++){
            maps.put(arr[i], maps.getOrDefault(arr[i], 0) + 1);
            while(maps.get(arr[i])>1){
                maps.put(arr[j], maps.getOrDefault(arr[j], 1) - 1);
                j++;
            }
            ans = Math.max(ans, i - j + 1);
        }
        System.out.println(ans);
    }
}

image-20210820203825252

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        int[] a = new int[n];
        int[] b = new int[m];
        for(int i = 0 ; i < n; i++){
            a[i] = in.nextInt();
        }
        for(int j = 0 ; j < m; j++){
            b[j] = in.nextInt();
        }
        String ans = "No";
        for(int i = 0, j = 0; j < m; j++){
            int tmp = b[j];
            if(tmp == a[i] && i < n){
                i++;
            }
            if(i == n){
                ans = "Yes";
                break;
            }
        }
        System.out.println(ans);
    }
}

image-20210820203943503

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        int x = in.nextInt();
        //System.out.println(n + " " + m);
        int[] a = new int[n];
        int[] b = new int[m];
        for(int i = 0; i < n; i++){
            a[i] = in.nextInt();
        }
        for(int j = 0; j < m; j++){
            b[j] = in.nextInt();
        }
        int ii = 0, jj = 0;
        for(int i = 0, j = m - 1; i < n; i++){
            int sum = a[i] + b[j];
            //System.out.println(sum + " ");
            while(sum >= x){
                j--;
                sum = a[i] + b[j];
                if(sum == x){
                    ii = i;
                    jj = j;
                    break;
                }
                System.out.println(sum + " ");
            }
        }
        System.out.println(ii + " " + jj);
    }
}

6、位运算

几种常用的位运算的操作:

求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n

主要是为了解决两类问题:

  • n的2进制表示中第k位是几?

    基本思路:先把第k位,移到个位(右移);再看一下个位是几
    image-20210820204410028

  • lowbit:返回x的最后一位1。举个例子?

    1010 -> 10
    101000 -> 1000
    

    表达形式是 x&-x为啥可以这么做呢?-x的二进制表示与x取反加1的表示是一样的,取反之后,1变为0,加完1之后,那个0之前的就不会进位l,0变为1了,之后的0取反之后变为1,加1之后变为0,取&之后就只留下了第一个1

在这里插入图片描述

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        var in = new Scanner(System.in);
        int n = in.nextInt();

        for(int i = 0 ; i < n; i++){
            int x = in.nextInt();
            int count = 0;
            while(x>0){
                x = x - lowbit(x);
                count++;
            }
            System.out.print(count + " ");
        }
    }
    
    private static int lowbit(int x){
        return x & -x;
    }
}

7、离散化

整数有序的离散化:

image-20210820211854424
一个映射的概念。需要注意两个问题:

  • 去重(存在重复元素的时候咋办)(:
  • 快速的映射(如何算出a[i]离散化之后的值)(二分):

image-20210820212040024

值域很大,但是很稀疏,可以用离散化来做。

用到过的数映射为从1开始的自然数就可以了。再用前缀和来解题。java使用ArrayList来实现,但是unit函数需要自己实现。java可以用集合去重?

排序之后去重用的是双指针算法
在这里插入图片描述

import java.util.*;

class Pair{
    int first;
    int second;
    public Pair(int first, int second){
        this.first = first;
        this.second = second;
    }
}

public class Main{
    public static void main(String[] args){
        var in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        
        List<Integer> alls = new ArrayList<Integer>();
        List<Pair> add = new ArrayList<>();
        List<Pair> query = new ArrayList<>();
        int[] ori = new int[300010];
        int[] sum = new int[300010];
        //读取数据
        for(int i = 0; i < n; i++){
            int a = in.nextInt();
            int b = in.nextInt();
            add.add(new Pair(a, b));
            alls.add(a);
        }
        for(int j = 0; j < m; j++){
            int l = in.nextInt();
            int r = in.nextInt();
            query.add(new Pair(l, r));
            alls.add(l);
            alls.add(r);
        }
        //排序出重。为啥这么做呢?是需要做前缀和。
        Collections.sort(alls);
        int unique = unique(alls);
        alls = alls.subList(0, unique);
        
        for(Pair item : add){
            int tag = find(item.first, alls);
            ori[tag] += item.second;
        }
        //前缀和
        for(int i = 1; i <= alls.size(); i++) sum[i] = sum[i - 1] + ori[i];
        
        for(Pair item : query){
            int l = find(item.first, alls);
            int r = find(item.second, alls);
            
            System.out.println(sum[r] - sum[l - 1]);
        }
    }
    public static int unique(List<Integer> list){
        int j = 0;
        for(int i = 0; i < list.size(); i++){
            if(i == 0 || list.get(i) != list.get(i - 1)){
                list.set(j, list.get(i));
                j++;
            }
        }
        return j;
    }
    
    public static int find(int x, List<Integer> list){
        int l = 0;
        int r = list.size() - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(list.get(mid) >= x){
                r = mid;
            }else{
                l = mid + 1;
            }
        }
        return l + 1;
    }
}

8、区间合并

用的情况没有那么多,但是也会用到:

  • 区间左端点排序
  • 扫描整个区间,维护一个当前的区间进行合并

在这里插入图片描述

import java.util.*;

class Pair implements Comparable<Pair>{
    int first;
    int second;
    public Pair(int a, int b){
        this.first = a;
        this.second = b;
    }
    @Override
    public int compareTo(Pair other){
        if(this.first < other.first){
            return -1;
        }else{
            return 1;
        }
    }
}

public class Main{
    public static void main(String[] args){
        var in = new Scanner(System.in);
        int n = in.nextInt();
        List<Pair> a = new ArrayList<>();
        
        for(int i = 0; i < n; i++){
            a.add(new Pair(in.nextInt(), in.nextInt()));
        }
        Collections.sort(a);
        /*
        for(Pair item : a){
            System.out.println(item.first + "," + item.second);
        }
        */
        int count = 0;
        int lmax = Integer.MIN_VALUE;
        for(int i = 0; i < a.size(); i++){
            Pair pair = a.get(i);
            if(pair.first > lmax){
                count++;
            }
            lmax = Math.max(lmax, pair.second);
        }
        System.out.println(count);
    }
}

总结

写的比较匆忙,推荐acwing这个平台吧。记录是为了更好的学习融会贯通。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值