目的:只是为了帮助一些上课的同学整理笔记,有什么错误也欢迎指出。
课程详情:活动 - AcWing
争取六月十日之前整理复习到dp
目录
1.排序(这部分可以不学,直接使用sort函数)
快速排序
思路:(基于分治)
1.确定分界点:a.直接确定左边距 b.直接确定右边界 c.直接确定中间值 d.随机;
2.调整区间(难点):使得所有小于等于x的在左边,使得所有大于等于x的在右边;
3.递归处理左右两段。
快速排序算法模板 —— 模板题 AcWing 785. 快速排序
import java.util.Scanner;
import java.io.BufferedInputStream;
public class 快速排序{
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 x = arr[l];//定义分界点;
//因为接下来的do while是先加减的,所以我们需要事先分别把i和j移开边界一位
int i = l - 1;
int j = r + 1;
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);
//也可以写成(arr,l,i-1)和(arr,i,r),但是就需要在定义分界点问题上改成x=arr[r],或者arr[(l+r)/2],否则会出现边界问题(比如:2,1 死循环)
//同理,当我们int x = arr[r]的时候也不能区间取(arr,l,j)和(arr,j+1,r)。
//即最好的情况:分界点取一边的时候,区间不能取同一边的坐标!
}
}
归并排序
思路:(基于分治)
1.确定分界点:mid = (l+r) /2
2.递归排序:left,right
3.归并——合二为一(时间复杂度:O(n))
总的时间复杂度:O(nlogn)
归并排序算法模板 —— 模板题 AcWing 787. 归并排序
import java.util.Scanner;
import java.io.BufferedInputStream;
public class Main {
public static void main(String args[]) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.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) {
//一、设置中间值
int mid = l + ((r - l) >> 1);
//二、递归
if (l >= r) {
return;
}//设置递归到底端的返回条件
//设置递归条件
//(l + r)改成l + (r - l)写法的好处:避免溢出;涉及负数范围时上下边界不统一;
// /2改成>> 1的好处:除以2是向0取整,右移一位是向下取整
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
//三、设置归并
int i = l;
int j = mid + 1;
int[] tmp = new int[r - l + 1];//创建可以存放对比两数列的空数组
int k = 0;//空数组指针
while (i <= mid && j <= r) {
//a.开始对比填数
if (arr[i] <= arr[j]) {
tmp[k++] = arr[i++];
}else {
tmp[k++] = arr[j++];
}
}
//b.之后再补上由于两数列指针一方已经结束后剩余另一方数列的余下数
while (i <= mid) {
tmp[k++] = arr[i++];
}
while (j <= r) {
tmp[k++] = arr[j++];
}
//c.将排好序的数组内容输入arr进行修改
for (i = l,j = 0; i <= r; i++,j++) {
arr[i] = tmp[j];
}
}
}
2. 二分
单调性,二段性是可以二分的充分非必要条件。
整数二分
思路:(边界问题,左右两边的边界不断缩短来找出寻找的数字所在的坐标区间)
当l = mid的时候要记得在mid计算中值的(l+r)后加上1,避免当lr相邻时候的死循环
开始最好先写成r = mid的形式,这样子可以保证退出循环的时候l和r都在数组的查询数字的最前一位,方便先输出正确的l(即先写第2个模板,再写第1个)
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;
}
import java.util.Scanner;
import java.io.BufferedInputStream;
public class 整数二分 {
public static void main(String args[]) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int q = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
while (q > 0) {
q--;
int x = sc.nextInt();
int l = 0;
int r = n - 1;
//进入循环,在l与r相同时退出
while (l < r) {
int mid = l + ((r - l) >> 1);
if (arr[mid] >= x) {
r = mid;
}else {
l = mid + 1;
}
}
//此时的l与r相同,需要判断是否数组内出现了想要查询的数字
if (arr[l] != x) {
System.out.println("-1 -1");
}else {
System.out.print(l);
//由于上面一直进行,l和r此时是相同的,所以需要再次计算边界
l = 0;
r = n - 1;
while (l < r) {
int mid = l + ((r - l + 1) >> 1);
if (arr[mid] <= x) {
l = mid;
//重点!!由于是l = mid,所以为了避免当l和r相邻情况下的除法下取整规律而导致的无限循环,所以需要加1来变成上取整
}else {
r = mid - 1;
}
}
System.out.println( " " + r);
}
}
}
}
AcWing 1460. 我在哪?(每日一题)
package 我在哪1460;
import java.util.*;
public class 我在哪1460{
static int n;
static String s;
public static boolean check(int mid){
Map<String, Integer> map = new HashMap<>();
int max = 0;
for (int i = 0; i <= n - mid; i ++){
String t = s.substring(i, i + mid);
if (map.get(t) == null) map.put(t, 1);
else map.put(t, map.get(t) + 1);
if (map.get(t) != 1) return false;
}
return true;
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
s = scan.next();
int l = 1, r = n;//出现相同的字符串长度,最小是1最大是n
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
System.out.println(l);
}
}
浮点数二分
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.前缀和
前缀和的作用是:能快速的算出数组的每一部分的连续和。
让S0 = 0两个优点:
-
生成前缀和很方便
-
计算i+~-j很方便。
Si-Sj-1
一维前缀和
S[i] = a[1] + a[2] + ... a[i] a[l] + ... + a[r] = S[r] - S[l - 1]
import java.io.*;
public class 前缀和 {
public static void main(String agrs[]) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] s = in.readLine().split(" ");
int n = Integer.parseInt(s[0]);
int m = Integer.parseInt(s[1]);
s = in.readLine().split(" ");
int[] arr = new int[n + 1];
for (int i = 1; i < n + 1; i++) {
arr[i] = Integer.parseInt(s[i-1]);
}
while (m-- > 0) {
s = in.readLine().split(" ");
int l = Integer.parseInt(s[0]);
int r = Integer.parseInt(s[1]);
int[] a = new int[r - l + 2];
a[0] = 0;
int k = 1;
for (int j = l; j <= r; j++) {
if (j == l) {
a[k] = arr[j];
}else {
a[k] = a[k - 1] + arr[j];
}
k++;
}
System.out.print(a[r - l + 1]);
}
}
}
二维前缀和
—— 模板题 AcWing 796. 子矩阵的和
S[i, j] = 第i行j列格子左上部分所有元素的和 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为: S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]