快速排序
- 确定分界点x (可以是左边界,右边界,中间随机)
- 将小于等于x的数放到左边,大于等于x的放右边
- 递归处理左右两端
如何处理第二步:
双指针法
模版:
public static void quickSort(int[]arr,int l,int r) {
if(l >= r) {
return;
}
int x= arr[l+r>>1]; //arr[l+r+1>>1] 后面递归要用i
int i = l - 1; //初始左指针位置
int j = r + 1; //初始右指针位置
while(j > i) {
do i++; while(arr[i] < x); //先将左指针向右移,在判断底下这个数是不是小于x的,是就继续右移
do j--; while(arr[j] > x); //先将右指针向左移,再判断底下这个数是否大于x,是就继续左移
if(j > i) swap(arr[i],arr[j]); //只有在下标满足条件的情况下才交换
}
quickSort(arr,l,j); //这里递归的边界取i还是j,与x取右边界还是左边界有关
quickSort(arr,j + 1,r); //x如果取的左边界,所以递归边界一定要用j,反之,一定要用i
/*
*quickSort(arr,l,i - 1);
quickSort(arr,i,r);
*/
}
最后结果可能是 i = j j < i
归并排序
- 以数组下标中间点为分界点
- 递归排序左边和右边
- 归并,合二为一
如何将这两个有序的序列合二为一:
双指针法:
直到有一条序列 到了底,没到底的序列剩下那部分直接接在结果序列后面即可
时间复杂度:nlog(n)
总共有logn层,每层时间复杂度都是On的,所以总时间复杂度为nlogn、
快排同理,虽然每次不一定是平分的,但是期望是平分的
模版:
public static void merageSort(int[]a,int l,int r) {
if(l >= r) return;
int mid = l + ((r - l) >> 1);
merageSort(a,l,mid);
merageSort(a,mid+1,r);
int[]temp = new int[r - l + 1];
int k = 0; //记录temp数组的元素
int i = l; //第一条序列开始的指针
int j = mid + 1; //第二条序列开始的指针
while(i <= mid && j <= r) {
if(a[i] <= a[j]) temp[k++] = a[i++];
else temp[k++] = a[j++];
}
//两指针谁没走到头,就继续将剩下的接在temp后面
while(i <= mid) temp[k++] = a[i++];
while(j <= r) temp[k++] = a[j++];
//最后将得到的排好序的数组赋值给原来的
for(i = l,j = 0;i <= r;i++,j++) {
a[i] = temp[j];
}
}
二分
模版:
while(l < r) {
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
while(l < r) {
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
先根据check函数判断处理结果是什么,再根据处理结果判断mid是否要补充加1
这里l 和 r最后结果都是一样的
浮点数二分
不需要考虑边界问题
public class Main{
public static void main(String[]args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
double f = Double.parseDouble(br.readLine());
double l = 0;
double r = Math.abs(f);
//如果要保留4位小数,写1e-6 保留5位 1e-7 经验值 比要求的多两位
//这里也可以不管就直接循环100次来代替用精度表示的迭代
while(r - l >= 1e-8) {
double mid = (l + r) / 2;
if(mid * mid * mid >= Math.abs(f)) r = mid;
else l = mid;
}
if(f >= 0) System.out.println(String.format("%.6f",r));
else System.out.println("-"+String.format("%.6f",r));
}
}
高精度
超过int的值 10的九次方
超过long的值 10的18次方
BigInteger
public BigInteger(int num,Random rnd) //获取随机大整数,范围0~ 2的num次方-1
public BigInteger(Stirng val)
public BigInteger(String val,int radix) //获取指定进制大整数
public static BigInteger valueOf(long val) //内部有优化,但超过long范围就不行了
//在内部对-16~16提前创建好了对象
对象一旦创建不能被改变,如果进行add,等操作,会产生新的BigInteger对象记录
add
subtract
multiply
divide #除法,获取商
divideAndRemainder(BigInteger val) #除法,获取商和余数,放在一个数组中返回
pow(int e) #次幂
max/min(BigInteger val)
intValue(BigInteger val) #转成int类型数据,超出范围数据有误
BigDecimal
public BigDecimal(double val)
public BigDecimal(String val)
public static BigDecimal valueOf()
# 对于0~10之间的整数,包含0 10 ,方法会返回已经创建好的对象,不会重新new
add
subtract
multiply
divide
BigDecimal divide(BigDecimal val,精确几位,舍入模式)
#舍入模式
四舍五入:RoundingMode.HALF_UP
其他在RoundingMode类中找即可
前缀和
就是再定义一个长度和要存放数据的数组(a)等长的数组(s),在初始化存放数据数组时,将这个数组也初始化,每个位置存放前一个位置(s[i - 1])和 a[i] 的和
其中a数组和s数组下标最好都从1开始,这样在初始化前缀和数组时,是s[i] = s[i - 1] + a[i] 这里的i - 1,就不用单独判断
差分
差分就是前缀和的逆运算,
作用:
可以用O1的时间复杂度,给原数组某个区间加上一个固定的值
处理:
如果想让数组[l , r] 区间的数都加上c,那么让diff[l] + c , 让 diff[r + 1] - c 即可
核心:
构造差分数组不是核心,差分数组的构造可以看做,将元素一个一个按照规则添加进一个全为0的差分数组中,核心是添加进差分数组的规则
-
一维差分
/** * 要添加数的区间 1 ~ r * 要添加的值 */ public static void insert(int[]a,int l,int r,int c) { a[l] += c; a[r+1] -= c; }
-
二维差分
/** * 要添加数的区间 x1,y1 ~ x2,y2 * 要添加的值 c */ public static void insert(int[][]d,int x1,int y1,int x2,int y2,int c) { d[x1][y1] += c; d[x2 + 1][y1] -= c; d[x1][y2 + 1] -= c; d[x2 + 1][y2 + 1] += c; }
双指针
所有双指针算法都是On的
模板:
for(i = 0,j = 0;i < n;i++) {
while(j < i && check(i,j)) j++;
//每道题目具体逻辑
}
核心思想:
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) {
}
}
//O(n^2)
将上面的枚举i,枚举j,优化到On级别
位运算
x = 10 n = 1010 (二进制表现形式)
源码:0……01010
反码:1……10101
补码:反码+1 1……10110
-
求n的二进制表示中第k位是几
# 将n右移k位,再&1 n >> k & 1
-
lowbit 操作:返回x的最后一位1,lowbit最高一位1,就是x最低一位1
x & -x
可以用来统计x中1的个数:每次将最后一个1去掉,减了几次就说明有几个1
离散化
基本含义:
对于值域较大,而个数相对较少的一组数据 a[ ] ,例如 值域 0~10^9 个数 :5
我们需要用到他的值作为下标,开一个10^9的数组显然不合理
所以我们需要将他映射到一个从0开始的连续的数组中(将他的值映射成下标),这个过程称为离散化
a[] 数组应该是有序的,整个过程要保序的进行
- 其中a数组可能存在重复元素,需要去重
- 如何算出a[i] 离散化后的值 (二分 找到第一个大于等于x的位置,即找右边界)
例题:
acwing:802 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n和 m。接下来 n行,每行包含两个整数 x和 c。
再接下来 m行,每行包含两个整数 l和 r。
输出格式
共 m行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9 ≤x≤ 10^9,
1≤n,m≤10^5,
−10^9≤ l ≤r ≤ 10^9,
−10000≤c≤10000
分析:
区间和这种题目对于数据量较少的可以直接使用前缀和,本题数据量较大,先进行离散化处理再使用前缀和
将需要用到的下标记录下来,将这些不连续的下标想办法映射成连续的数字(将这些下标排完序后返回下标对应索引即可,可以用indexOf,但是效率较低可能超时,最好用二分查找)
这里x的范围是 -10的九次方到10的九次方 ,而需要用到的下标数量最多为 n + 2m (添加的时候访问n次下标,询问的时候一次访问两个,就是2m)也就是30万,符合离散化的特性
区间合并
给多个区间,如果区间之间有交集的话,将他们合并成一个区间
处理方式:
先将所有区间以左端点从大到小排序,维护一个以st 开头,ed 结尾的区间 x,遍历所有区间
如果遍历到的区间 item,起始点大于x的ed,说明当前维护区间和后面的区间没有关系,合并完成一个区间,否则比较两区间的ed,将较大的那个作为当前维护区间的ed