计算模板
进制转换
//将b进制的数转换为十进制
public static int get(String s, int b){
int res = 0;
for (char c : s.toCharArray()){
res = res * b + c - '0';
}
return res;
}
快速幂
//预处理出 a^2^0,a^2^1,a^2^2,...,a^2^logk 这k个数
//将a^k用这b种数来组合,即a^k = a^2^x1 * a^2^x2 * ... * a^2^xt = a^(2^x1 + 2^x2 + ... + 2^xt)
public double quickMi(double a, long k){
double res = 1;
while(k > 0){
//如果k的二进制表示的第0位为1, 则乘上当前的a
if((k & 1) == 1) res *= a;
//右移一位
k >>= 1;
//更新a,a依次为a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
a *= a;
}
return res;
}
质数
public boolean isPrime(int x) {
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
}
//[2,n)的所有质数, 若[2,n] 则 i <= n i * res.get(j) <= n
public int countPrimes(int n) {
boolean[] vis = new boolean[n + 1];
List<Integer> res = new ArrayList<>();
for(int i = 2; i < n; i++){
if(!vis[i]) res.add(i);
for(int j = 0; i * res.get(j) < n; j++){
vis[i * res.get(j)] = true;
if(i % res.get(j) == 0) break;
}
}
return res.size();
}
基础算法
前缀
int[] a = new int[N];
int[] s = new int[N];
//原数组,下标从1开始
for (int i = 1; i <= n; i++)
a[i] = input.nextInt();
//s[i]代表 a的前i项和
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + a[i];
while(m-- > 0){
int l = input.nextInt();
int r = input.nextInt();
//[l,r]中的和
System.out.println(s[r] - s[l - 1]);
}
差分
int[] s = new int[n + 2];
int[] b = new int[n + 2];
//存放原数组
for (int i = 1; i <= n; i++){
s[i] = input.nextInt();
}
//构建差分数组
for (int i = 1; i <= n; i++){
b[i] = s[i] - s[i-1];
}
for (int i = 0; i < m; i++){
int l = input.nextInt();
int r = input.nextInt();
int c = input.nextInt();
//将l和以后加c, 将r之后-c
b[l] += c; b[r + 1] -= c;
}
for (int i = 1; i <= n; i++){
//将差分改为原数组
b[i] += b[i - 1];
}
//当前的坐标
int now = 0;
// 求多少个单位至少被涂抹2层油漆
while (n-- != 0){
//当前移动的距离
int dis = input.nextInt();
char ch = input.next().charAt(0);
if (ch == 'R'){
//map[now]++;
map.put(now,map.getOrDefault(now,0) + 1);
//map[now+dis]--;
map.put(now + dis,map.getOrDefault(now + dis,0) - 1);
//向右移动,坐标变化
now += dis;
}else {
//向右移动
//map[now-dis]++;
map.put(now - dis,map.getOrDefault(now - dis,0) + 1);
//map[now]--;
map.put(now,map.getOrDefault(now,0) - 1);
//向左移动,坐标变化
now -= dis;
}
}
//res表示答案, sum表示前缀和, last表示上一次的位置
int res = 0, sum = 0, last = 0;
for (int x : map.keySet()){
//[x-last]表示坐标之前的距离 (TreeMap已经排序过)
if (sum >= 2) res += (x - last);
//每一次加上下一点的下标的value的值,如果刷过两遍的区间,求出长度的个数
sum += map.get(x);
last = x;
}
System.out.println(res);
二分
//大于等于x的第一个位置
int l = 0, r = n - 1;
while (l < r){
int mid = l + r >> 1;
if (a[mid] < x) l = mid + 1;
else r = mid;
}
//大于x的第一个位置
l = 0; r = n - 1;
while (l < r){
int mid = l + r >> 1;
if (a[mid] <= x) l = mid + 1;
else r = mid;
}
对二分不同版本的理解
假设目标值在 [l,r] 中, 假设 M 是我们最后要的答案,那么这个区间就会被 M 分成两个部分
在判断的时候将 M 放在左半部分还是右半部分,决定着代码会是一个怎么的样子, 而这两个模板就是针对这两种情况而提供的
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid;
}
return l;
}
版本一是将区间 [l,r] 划分成 [l,mid] 和 [mid+1,r] 时(即 M 属于右半部分的时候),其更新操作是 r = mid 或者
l = mid+1,计算 mid 时不需要加 1
这个模板中为什么 l = mid + 1呢?
因为当给 l 赋值的时候,是 mid不在区间的右半部分的时候。又因为答案 M 是在右半部分,所以可以知道 mid 一定不是我们要的答案,因此 l = mid + 1(满足区间相邻两个数差值为 1时的情况下 +1)
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;
}
版本一是将区间 [l,r] 划分成 [l,mid - 1] 和 [mid,r] 时(即 M 属于左半部分的时候),其更新操作是 r=mid - 1或者 l = mid,此时为了防止死循环,计算 mid 时需要加 1
对于为什么 r = mid − 1 ,当给 r 赋值的时候,是 mid不在区间的左半部分的时候。又因为答案 M 是在左半部分,所以可以知道 mid 一定不是我们要的答案,因此 r = mid − 1(这里是满足区间相邻两个数差值为 1时的情况下 +1)
那 mid = l + r + 1 >> 1是怎么回事呢?
若 l + 1 = r 时,我们采用 mid = l + r >> 1 会出现一个什么样的情况?
∵mid = l + r = (2 ∗ l + 1 ) / 2 = l
∴l = mid = l
∴区间范围依然是[l,r]
所以为了出现这种死循环的情况,计算 mid = l + r + 1 >> 1
数据结构
kmp
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
排序算法
快速排序
思路:将待排序的序列分成前后两部分,其中前一部分的数据都比后一部分的数据要小,然后再递归调用函数对两部分的序列分别进行快速排序,以此使整个序列达到有序
时间复杂度:整体时间复杂度 O(n log n) ,如果运气不好,每次都选择最小值作为基准值,那么每次都需要把其他数据移到基准值的右边,递归执行 n 行,运行时间也就成了 O(n^2)
public static void quickSort(int []arr,int left,int right){
if (left >= right)
return;
int i = left - 1, j = right + 1, x = arr[left + right >> 1];
while (i < j){
do i++; while (arr[i] < x);
do j--; while (arr[j] > x);
if (i < j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
quickSort(arr, left, j);
quickSort(arr,j+1,right);
}
堆排序
思路:首先将待排序的数组构造成一个大根堆(升序用大根堆,降序就用小根堆),即整个数组的最大值就是堆结构的顶端。将顶端的数与末尾的数交换,即末尾的数为最大值,剩余待排序数组个数为 n - 1。将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
时间复杂度: O(n log n)
public class HeapSort {
public static void heapSort(int[] arr){
// 构造大根堆
heapInsert(arr);
int size = arr.length;
while (size > 1){
// 固定最大值
swap(arr,0,size - 1);
size--;
// 构造大根堆
heapify(arr,0,size);
}
}
// 构造大根堆 (通过新插入的数上升)
public static void heapInsert(int[] arr){
for (int i = 0; i < arr.length; i++){
// 当前插入的索引
int index = i;
// 父节点索引
int father = (index - 1) / 2;
// 如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
// 然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
while (arr[index] > arr[father]){
// 交换当前结点与父结点的值
swap(arr,index,father);
// 将当前索引指向父索引
index = father;
// 重新计算当前索引的父索引
father = (index - 1) / 2;
}
}
}
// 将剩余的数构造成大根堆(通过顶端的数下降)
public static void heapify(int[] arr, int index, int size){
int left = index * 2 + 1, right = index * 2 + 2;
while (left < size){
int largestIndex;
// 判断孩子中较大的值的索引 (要确保右孩子在size范围之内)
if (arr[left] < arr[right] && right < size){
largestIndex = right;
}else {
largestIndex = left;
}
// 比较父结点的值与孩子中较大的值,并确定最大值的索引
if (arr[index] > arr[largestIndex]){
largestIndex = index;
}
// 如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
if (index == largestIndex){
break;
}
// 父结点不是最大值,与孩子中较大的值交换
swap(arr,largestIndex,index);
//将索引指向孩子中较大的值的索引
index = largestIndex;
// 重新计算交换之后的孩子的索引
left = index * 2 + 1;
right = index * 2 + 2;
}
}
public static void swap(int[]arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
归并排序
将待排序的序列 分成 长度相同的两个子序列 ,当无法继续往下分时(也就是每个子序列中只有一个数据时),就对子序列进行归并。归并指的是把两个排好序的子序列合并成一个有序序列。该操作会一直重复执行,直到所有子序列都归并为一个整体为止
无论哪一行都是 n 个数据,所以每行的运行时间都为 O(n)。而将长度为 n 的序列对半分割直到只有一个数据为止时,可以分成 log2n 行,因此,总共有 log2n 行。也就是说,总的运行时间为 O(nlogn)
public static void mergeSort(int []arr,int left,int right) {
if (left >= right) return;
int mid = left + right >> 1;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
int[] temp = new int[right - left + 1]; // 临时数组, 用于临时存储 [l,r]区间内排好序的数据
int k = 0, i = left, j = mid + 1; //两个指针
while (i <= mid && j <= right) {
if (arr[i] <= arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
//进行赋值
for (i = left,j = 0;i <= right;i++,j++)
arr[i] = temp[j];
}