1. 快速排序
🤠 原题链接
🤠 快速排序+选择性递归
🤠 O( 2n )
import java.util.*;
class Main{
static int N = 1010;
static int[] q = new int[N];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
for(int i =0; i < n; i++)
q[i] = sc.nextInt();
System.out.println(quickSort(0,n-1,k));
}
static int quickSort(int l,int r,int k){
if(l==r) return q[l];//递归出口
int x = q[l];//分界点
int i = l-1;//左边界
int j = r+1;//右边界
// 快速排序
while(i<j){
while(q[++i]<x);//从左到右找到一个大于 x 的数
while(q[--j]>x);//从右到左找到一个小于 x 的数
if(i<j){
int t = q[i];
q[i] = q[j];
q[j] = t;
}
}
int sl = j-l+1;//左边区间的长度
if(k <= sl)
return quickSort(l,j,k);//递归左区间
return quickSort(j+1,r,k-sl);//递归右区间
}
}
2. 归并排序
🤠 参考链接
🤠 归并求逆序对(分而治之)
static int N = 100010;
static int[] q = new int[N], tmp = new int[N];
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for (int i = 0; i < n; i++)
q[i] = sc.nextInt();
System.out.println(mergeSort(0, n - 1));
}
/**
* @param l 区间左边界
* @param r 区间右边界
* @return long 类型的区间内的逆序对
*/
private static long mergeSort(int l, int r)
{
// 递归出口
if (l >= r)
return 0;// 区间只有一个数了,没有逆序队
// 分
int mid = l + r >> 1;
long res = mergeSort(l, mid) + mergeSort(mid + 1, r);
// 治 (归并)
int k = 0;// 临时数组指针
int i = l;// 左区间指针
int j = mid + 1;// 右区间指针
while (i <= mid && j <= r)
{
if (q[i] <= q[j])// 无逆序对
tmp[k++] = q[i++];
else
{// 右边区间的数比左边区间的数小,有逆序对
tmp[k++] = q[j++];
res += mid - i + 1;
}
}
// 断后
if (i <= mid)
tmp[k++] = q[i++];
if (j <= r)
tmp[k++] = q[j++];
// 物归原主
for (i = l, j = 0; i <= r; i++, j++)
{
q[i] = tmp[j];
}
return res;
}
3. 二分
🤠 参考链接
import java.util.*;
public class 数的三次方根
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
double n = sc.nextDouble();
double l = -10000, r = 10000;
// 浮点数二分,有可能无穷,所以只要 l r 无穷接近即可,具体的精度按题目所给+2(保险)
while (r - l > 1e-8)
{
double m = (r + l) / 2;// 浮点数 不能用位移
if (m * m * m > n)
// 边界更新为同一个点
r = m;
else
{
// 边界更新为同一个点
l = m;
}
}
// 保留 6 位小数
System.out.printf("%.6f", l);
}
}
4. 前缀和
🤠 原题链接
🤠 避坑:花一天可以浇 n 次,不仅仅是 0 1 2
import java.util.*;
public class Main
{
static int N = (int) 1e5 + 10;
static int[] A = new int[N];// 差分数组
static int[] B = new int[N];// 前缀和数组
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();// 假期天数
int m = sc.nextInt();// 人数
for (int i = 0; i < m; i++)
{
int a = sc.nextInt();
int b = sc.nextInt();
A[a] += 1;
A[b + 1] -= 1;
}
for (int i = 1; i <= n; i++)
{
B[i] = A[i] + B[i - 1];
}
int k = 1;
for (k =1; k <= n; k++)
{
if (B[k] == 0 || B[k] == 2)
{
System.out.print(k + " " + B[k]);
break;
}
}
if (k == n+1)
System.out.print("OK");
}
}
5. 二维前缀和
🤠 参考链接
import java.util.*;
public class 子矩阵的和
{
static int N = 1010;
// 坐标表示 格子
static int[][] a = new int[N][N];// 原数组
static int[][] s = new int[N][N];// 前缀和数组
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int q = sc.nextInt();
// 下标从 1 开始 ,因为求前缀和数组需要用到 n-1
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
a[i][j] = sc.nextInt();
// 预处理前缀和数组
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
while (q-- > 0)
{
// [x1,y1] 左上角的格子 ;[x2,y2] 右下角的格子
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
int res = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
System.out.println(res);
}
}
}
6. 差分
🤠 参考链接
import java.util.*;
public class 差分
{
static int N = 100010;
static int[] a = new int[N];// 差分数组
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 1; i <= n; i++)
{
insert(i, i, sc.nextInt());
}
while (m-- > 0)
{
int l = sc.nextInt();
int r = sc.nextInt();
int c = sc.nextInt();
insert(l, r, c);
}
for (int i = 1; i <= n; i++)
{
a[i] += a[i - 1];
System.out.print(a[i] + " ");
}
}
public static void insert(int l, int r, int c)
{
// 在 区间 l~r 上加上 c
a[l] += c;
a[r + 1] -= c;
}
}
7. 二维前缀和
🤠 参考地址
import java.util.Scanner;
public class 差分矩阵
{
static int N = 1010;
static int[][] a = new int[N][N];// 前缀和
static int[][] b = new int[N][N];// 差分数组
/**
* @param x1 左上角横坐标
* @param y1 左上角纵坐标
* @param x2 右下角横坐标
* @param y2 右下角纵坐标
* @param c 在该区间内(的前缀和)添加的常数
*/
static void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;// 左上角
b[x1][y2 + 1] -= c;// 右上角
b[x2 + 1][y1] -= c;// 左下角
b[x2 + 1][y2 + 1] += c;// 右下角
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();// 行数
int m = sc.nextInt();// 列数
int q = sc.nextInt();// 查询数
// 预处理差分数组
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
insert(i, j, i, j, sc.nextInt());
while (q-- > 0)
{
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
int c = sc.nextInt();
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
System.out.print(a[i][j] + " ");
}
System.out.println();
}
}
}
8. 双指针
🤠 数组元素的目标和
🤠 写个暴力 ——》 单调性 ——》双指针
import java.util.*;
public class 数组元素的目标和
{
static int N = 10010;
static int[] a = new int[N];
static int[] b = new int[N];
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int x = sc.nextInt();
for (int i = 0; i < n; i++)
a[i] = sc.nextInt();
for (int i = 0; i < m; i++)
b[i] = sc.nextInt();
// 双指针:有单调性
for (int i = 0, j = m - 1; i < n; i++)
{
while (a[i] + b[j] > x)
j--;
if (a[i] + b[j] == x)
{
System.out.println(i + " " + j);
break;
}
}
}
}
9. 数组实现单链表
🤠 参考链接
import java.util.*;
public class 数组实现单链表
{
static int N = 100010;
static int[] e = new int[N];// 节点数组,存储 i 节点的值
static int[] ne = new int[N];// next 数组,存 i 节点的下一个节点对应的(节点)数组下标
static int head, idx;// head 是头指针,idx 是节点数组的尾坐标
// 链表的初始化
static void init()
{
head = -1;// 表示链表为空
idx = 0;
}
/**
* 头插法
*
* @param x 把元素 x 添加到链表中
*/
static void add(int x)
{
e[idx] = x;// 节点数组存值
ne[idx] = head;// 对应的 next指针存 head
head = idx++;// 更新 头指针 为 idx
}
/**
* @param k 删除第 k 个数后面那个数
*/
static void del(int k)
{
ne[k] = ne[ne[k]];
}
/**
* @param k 在第k位后面插入
* @param x 值位 x 的元素
*/
static void ins(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];// 让 ne[idx] 代替 k 指向 ne[k]
ne[k] = idx++;
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
init();
while (m-- > 0)
{
String s = sc.next();
switch (s)
{
case "H":
add(sc.nextInt());
break;
case "D":
// 注意数组下标从 0 开始,减一(删除头结点得特判)
int k = sc.nextInt();
if (k == 0)
{
head = ne[head];
} else
{
del(k - 1);
}
break;
case "I":
// 注意数组下标从 0 开始,减一
ins(sc.nextInt() - 1, sc.nextInt());
break;
default:
break;
}
}
for (int i = head; i != -1; i = ne[i])
{
System.out.print(e[i] + " ");
}
}
}
10. 数组实现双链表
🤠 参考链接
🤠 0 表示左端点,1表示右端点
import java.util.*;
public class 数组实现双链表
{
static int N = 10010;
static int[] e = new int[N];
static int[] l = new int[N];
static int[] r = new int[N];
static int idx;
/**
* 链表初始化
*/
static void init()
{
// 0 表示左端点, 1 表示右端点
r[0] = 1;
l[1] = 0;
idx = 2;// 数组下标从 2 开始
}
/**
* @param k 在第 k 个节点的后边加上 x
* @param x
*/
static void add(int k, int x)
{
e[idx] = x;// 存值,指针为 idx
r[idx] = r[k];// 右指针指向 k 的右节点
l[idx] = k;// 左指针指向 k
l[r[k]] = idx;// k的右节点的左指针指向 idx
r[k] = idx;// k的右指针指向 idx
idx++;
}
/**
* @param k 删除第 k 个插入的数
*/
static void remove(int k)
{
r[l[k]] = r[k];// 左节点的右指针指向右节点
l[r[k]] = l[k];// 右节点的左指针指向左节点
}
public static void main(String[] args)
{
// 数组实现链表,第一步切记初始化
init();
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-- > 0)
{
String op = sc.next();
if ("L".equals(op))
{
int x = sc.nextInt();
add(0, x);// 0 始终表示 左端点
} else if ("R".equals(op))
{
int x = sc.nextInt();
add(l[1], x);// 1始终表示 右端点
} else if ("D".equals(op))
{
int k = sc.nextInt();
remove(k + 1);
} else if ("IL".equals(op))
{
int k = sc.nextInt();
int x = sc.nextInt();
add(l[k + 1], x);
} else if ("IR".equals(op))
{
int k = sc.nextInt();
int x = sc.nextInt();
add(k + 1, x);
}
}
// 从左端点 0 的右指针开始,直到右端点 1 结束
for (int i = r[0]; i != 1; i = r[i])
{
System.out.print(e[i] + " ");
}
}
}
11. 单调队列
🤠 参考链接
import java.util.*;
public class 滑动窗口_单调队列
{
static int N = 1000010;
static int[] a = new int[N];
static int[] q = new int[N];// 单调队列,维护 的是a数组的下标
static int hh, tt;// 队首(先入为首),队尾
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();// 数组长度
int k = sc.nextInt();// 窗口长度
for (int i = 0; i < n; i++)
{
a[i] = sc.nextInt();
}
// 队列初始化
hh = 0;
tt = -1;
// 求区间 [ i-k+1,i ] 的最小值
for (int i = 0; i < n; i++)// i 枚举的是窗口的右端点,i-k+1 是左端点
{
if (hh <= tt && q[hh] < i - k + 1)
hh++;// 防止队头滑出窗口
// 维护一个严格单调递增的队列
while (hh <= tt && a[q[tt]] >= a[i])
tt--;
q[++tt] = i;
// 窗口右端点下标 +1 即窗口可维护的最大元素个数
if (i + 1 >= k)
System.out.print(a[q[hh]] + " ");
}
System.out.println();
// 队列初始化
hh = 0;
tt = -1;
// 求区间最大值
for (int i = 0; i < n; i++)
{
if (hh <= tt && q[hh] < i - k + 1)
hh++;
// 维护严格严格递减的区间,= 也不行
while (hh <= tt && a[q[tt]] <= a[i])
tt--;
q[++tt] = i;
if (i + 1 >= k)
System.out.print(a[q[hh]] + " ");
}
}
}
12. KMP
🤠 KMP字符串匹配
import java.util.*;
class KMP
{
static int N = 1000010;
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
String s1 = sc.next();// 源串
String s2 = sc.next();// 子串
// 求 next 数组
int[] next = new int[s2.length()];
next[0] = 0;
// i 表示第几位,j 表示匹配数量
for (int i = 1, j = 0; i < next.length; i++)
{
// 这里让 j 跳回到 j 的前缀 与 i 的前缀相等的位置
while (j > 0 && s2.charAt(i) != s2.charAt(j))
j = next[j - 1];//j 更新为前缀已经匹配好的字符个数
// i 位 于 j 位相匹配,匹配数 +1
//注意 j 位 表示的是 第 j+1 位字符,前边有 j 位字符已经匹配好了
if (s2.charAt(i) == s2.charAt(j))
j++;
// 更新当前位的匹配数量
next[i] = j;
}
for (int i = 0, j = 0; i < s1.length(); i++)
{
while (j > 0 && s1.charAt(i) != s2.charAt(j))
j = next[j - 1];
if (s1.charAt(i) == s2.charAt(j))
j++;
if (j == s2.length())
{
System.out.println(i - j + 1 + 1);
j = next[j - 1];
}
}
System.out.println();
for (int i = 0; i < s2.length(); i++)
{
System.out.print(next[i] + " ");
}
}
}
13. 字典树
① 字串匹配
🤠 从前有棵树,树的每个节点有 26 棵子树,存纯字母字符串,可以公用相同前缀节省空间的效果
static int idx; // 当前 son二维数组中的 一维下标(即第一个[ ](方便开辟空间)
static int[][] son = new int[10][26]; // 结点数组 一维:树的层数 二维:字符的对应数值(特殊的next指针)
static int cnt[] = new int[10]; // 记录以该点结束的字符串有多少个
// trie树的插入操作
static void insert(char[] str) {
//所处层数,初始化从 0 开始
int p = 0;
for (int i = 0; i < str.length; i++) {
int u = str[i] - 'a'; // 把字符转换为0到25的数字表示
if (son[p][u] == 0)
//以前没有开辟字符,直接新开辟一个一维数组空间,即二维数组的一个下标
son[p][u] = ++idx;
p = son[p][u];
}
cnt[p]++;
}
// 查询操作
static int query(char str[]) {
int p = 0;
for (int i = 0; i < str.length; i++) {
int u = str[i] - 'a';
if (son[p][u] == 0) //一个字符不符合,直接pass
return 0;
p = son[p][u];
}
return cnt[p];
}
② 最大异或对
🤠 核心:二维数组的每一个一维数组都是一个 节点
🤠 最大异或对
import java.util.Scanner;
public class 最大异或对
{
static int N = 100010;
static int M = 31 * N;//表示总节点个数
// 存的是子节点的一维地址idx,这里每一个 int[]{0,1} 都表示一个节点
static int[][] son = new int[M][2];
static int idx;
/**
* @param x 待插入的数
*/
static void insert(int x)
{
int p = 0;// p 就是一个 idx ,所以 idx 是从1开始的
for (int i = 30; i >= 0; i--)
{
int k = x >> i & 1;// 取出第 i 位上的数
if (son[p][k] == 0)// 该位置为空
{
son[p][k] = ++idx;//0 已经被使用了,初始化成0,只能先 ++
}
p = son[p][k];
}
}
/**
* @param x 在trie树中 查找能与 x 亦或 得到最优的结果的数 res
* @return 返回该数 res
*/
static int query(int x)
{
int res = 0;
int p = 0;
for (int i = 30; i >= 0; i--)// 用 i 取出 参数 x 的每一位
{
int k = x >> i & 1;// x在第 i 位上的数,先从最高位开始取
// 1 0 之间的转换:1+0=1 ; 1-1=0 ,1-0=1 即可实现转换
if (son[p][1 - k] != 0)// 尽量往不同的分支走,以达到异或值最大
{
p = son[p][1 - k];
res = (res << 1) + (1 - k);
} else
{
p = son[p][k];
res = (res << 1) + k;
}
}
return res;
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int res = 0;
for (int i = 0; i < n; i++)
{
int x = sc.nextInt();
int y = query(x);
res = Math.max(res, x ^ y);
insert(x);
}
System.out.println(res);
}
}
③ 连续区间最大异或和
🤠 原题地址
🤠 核心:前缀异或和数组:任一个数 s[i] 异或前一个数 s[j] 都是连续区间,即区间【 i ~ j+1 】
import java.util.*;
public class 最大异或和2
{
static int N = 100010;
// 树的节点数组,有 N 个数,每个数就有 31个节点,每个节点有 2 颗子树
static int[][] son = new int[N * 32][2];
static int[] cnt = new int[N];// 当 cnt[i] == 0时,说明没有数经过此点,即不合法
static int idx, n, m;
static int[] s = new int[N];// 前缀异或和数组
/**
* @param x 待插入或删除的数
* @param f 1是插入,-1是删除
*/
static void insert(int x, int f)
{
int p = 0;
for (int i = 30; i >= 0; i--)
{
int k = x >> i & 1;
if (son[p][k] == 0)// 没有可用的适合节点,新建一个,有则复用
{
son[p][k] = ++idx;
}
p = son[p][k];
cnt[p] += f;
}
}
/**
* @param x
* @return 有效数字的最大异或和 (不合法数字已经被删除了,所以可以直接算贪心每一位取最优即可)
*/
static int query(int x)
{
int p = 0;
int res = 0;
for (int i = 30; i >= 0; i--)
{
int k = x >> i & 1;
if (cnt[son[p][1 - k]] > 0)
{
// 只要有相对的数,异或后的结果当前为必 +1
res = res * 2 + 1;
p = son[p][1 - k];
} else
{
res = res * 2;
p = son[p][k];
}
}
return res;
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 1; i <= n; i++)
{
s[i] = s[i - 1] ^ sc.nextInt();
}
insert(0, 1);// 任何数 ^ 0 都等于 它本身
int res = 0;
for (int i = 1; i <= n; i++)
{
if (i > m)
insert(s[i - m - 1], -1);// 把超出范围的前缀异或和 删掉
res = Math.max(res, query(s[i]));
insert(s[i], 1);
}
System.out.println(res);
}
}
14. 并查集
① 食物链
🤠 食物链
🤠 多维护一个 距离 表示元素间的关系
import java.util.Scanner;
public class Main
{
static int N = 50010;
static int[] d = new int[N];// 距离数组,存的是 到父节点的距离
static int[] p = new int[N];// 并查集数组,存的是 特定点的父节点
/**
* 返回 x 的根节点
*
* @param x
* @return
*/
static int find(int x)
{
if (p[x] != x)// 如果 x 不是根节点
{
int tmp = find(p[x]);//find过程也会改变 p[x]
d[x] += d[p[x]];
p[x] = tmp;
}
return p[x];
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 初始化,每一个点都是一个独立的集合
for (int i = 1; i <= n; i++)
p[i] = i;
int res = 0;
while (m-- > 0)
{
int t = sc.nextInt();
int x = sc.nextInt();
int y = sc.nextInt();
if (x > n || y > n)
{
res++;
continue;
}
// x y 的根节点
int px = find(x);
int py = find(y);
if (t == 1)
{// 同类
// px 和 py 在同一集合的情况
if (px == py && (d[x] - d[y]) % 3 != 0)// 不等于 0 就不是同类
res++;
// px 和 py 不在同一个集合的情况
else if (px != py)
{
p[px] = py;// 合并并查集
// d[px]:px到根 py的距离
// d[y]:y到根节点 py 的距离
// d[x]:x到根节点 px 的距离
d[px] = d[y] - d[x];// x y 同类,得 (d[x]+d[px]-d[y]) % 3 == 0;
}
}
// 异类
else
{
// d[x]-d[y] 很可能是负数,为了避免负数取模的多种可能性, -1,就可用 != 0 代替 !=1
if (px == py && (d[x] - d[y] - 1) % 3 != 0)
res++;
else if (px != py)
{
p[px] = py;
// x 吃 y ,按规定,(d[x] + d[px] - d[y] -1 )%3 == 0
d[px] = d[y] - d[x] + 1;
}
}
}
System.out.println(res);
}
}
② 最大度
🤠 原题地址
🤠 要建一个图,使得图中一个节点的度达到最大,那就是 菊花图,度 == 结点数-1
import java.io.*;
import java.util.*;
public class Main
{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
static int n, d, cnt;// cnt记录多出的边的数量
static int N = 1010;
static int[] p = new int[N];
static int[] s = new int[N];
static int[] max = new int[N]; // 每个集合中节点的最大度数
static int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
public static void main(String[] args) throws IOException
{
String[] split = in.readLine().split(" ");
n = Integer.parseInt(split[0]);
d = Integer.parseInt(split[1]);
// 初始化并查集
for (int i = 1; i <= n; i++)
{
p[i] = i;
s[i] = 1;
}
// 满足需求
while (d-- > 0)
{
String[] split2 = in.readLine().split(" ");
int x = Integer.parseInt(split2[0]);
int y = Integer.parseInt(split2[1]);
// 插入一条边,记得先 find 一次,再实现两个集合的合并
x = find(x);
y = find(y);
if (x == y)
cnt++;
else
{
p[x] = y;
s[y] += s[x];
}
// 选 i + cnt + 1 个最大的集合,总和减一就是答案
int tt = 0;
for (int j = 1; j <= n; j++)
{
if (find(j) == j)
max[tt++] = s[j];
}
// sort 默认就是升序
Arrays.sort(max, 0, tt);
int sum = 0;
// cnt条边,可以连通 cnt+1 个集合
for (int j = 0; j < tt && j < cnt + 1; j++)
{
// 优先取最大值
sum += max[tt - j - 1];
}
System.out.println(sum - 1);
}
}
}
15. BFS
🤠 八数码
import java.io.*;
import java.util.*;
public class Main
{
static int dx[] = { 1, 0, -1, 0 };
static int[] dy = { 0, 1, 0, -1 };
public static void main(String[] args) throws IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] aa = in.readLine().split(" ");
String start = "";
for (int i = 0; i < aa.length; i++)
{
start += aa[i];
}
int bfs = bfs(start);
if (bfs == -1)
System.out.println("dluldrurulldrrulldrrdllurrulddr");
else
{
System.out.println(bfs);
}
}
private static int bfs(String start)
{
// string end = "12345678x";表示终点状态
Queue<String> q = new LinkedList<>();// 维护状态
HashMap<String, Integer> d = new HashMap<>();// 存到 此状态 的 距离
q.offer(start);// 入队
d.put(start, 0);
String end = "12345678x";
// 宽搜
while (!q.isEmpty())
{
String s = q.poll();
int distance = d.get(s);
if (s.equals(end))
{
return distance;
}
int k = s.indexOf("x");// x 字符串中的位置
int x = k / 3, y = k % 3;// x 在矩阵中的 二维坐标
for (int i = 0; i < 4; i++)
{
int xx = x + dx[i];
int yy = x + dy[i];
if (xx >= 0 && xx < 3 && yy >= 0 && yy < 3)
{
char[] arr = s.toCharArray();
swap(arr, 3 * xx + yy, k);
// 错误实例
// String ss = arr.toString();//[, C, @, 3, 8, 0, f, b, 4, 3, 4]
// String ss = Arrays.toString(arr);//[2, 3, 4, 1, 5, 6, 7, x, 8]
String ss = new String(arr);// 字符数组转成字符串
if (d.get(ss) == null)// hashmap中没存有 ss 返回 null
{
d.put(ss, d.get(s) + 1);
q.offer(ss);
}
}
}
}
return -1;
}
public static void swap(char[] a, int i, int j)// 把数组的第i位与第j位互换
{
char tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
16. 高斯消元
import java.io.*;
import java.util.*;
public class 高斯消元
{
static int N = 4;
static int[][] a = new int[N][N];
static int n;
public static void main(String[] args) throws NumberFormatException, IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(in.readLine());
for (int i = 0; i < n; i++)
{
String[] split = in.readLine().split(" ");
for (int j = 0; j <= n; j++)
{
a[i][j] = Integer.parseInt(split[j]);
}
}
int t = gauss();
if (t == 0)
for (int i = 0; i < n; i++)
System.out.println(a[i][n]);
else if (t == 1)
System.out.println("Multiple sets of solutions");
else
{
System.out.println("No solution");
}
}
/**
* @return 0 表示唯一解 1 表示无穷多组解 2 表示无解
*/
static int gauss()
{
int r, c;// 表示行列数
for (r = 0, c = 0; c < n; c++)
{
int t = r;// 临时变量记录特定行
// 找出当前列 为 1 的任意一行(靠上面)
for (int i = r; i < n; i++)// i 枚举行
{
if (a[i][c] == 1)
{
t = i;
break;
}
}
// 当前列所有数都为 0
if (a[t][c] == 0)
continue;
// 交换 找到的 当前列值为 1 的行 与当前行交换
for (int i = c; i <= n; i++) // 按列交换
{
int tem = a[t][i];
a[t][i] = a[r][i];
a[r][i] = tem;
}
// 对当前行以下的行进行消0操作
for (int i = r + 1; i < n; i++)
{
if (a[i][c] == 1)
{
// j 从 c 开始是因为在 当前列 c 之前的数已经被前面的 循环 消 0 了
for (int j = c; j <= n; j++)
{
a[i][j] ^= a[r][j];// 1 ^ 1 = 0;
}
}
}
// r++处理下一行
r++;
}
// 有continue,有某一列都是 0 ,即缺少一个变量
if (r < n)
{
for (int i = r; i < n; i++)
{
// 如果常数都不为 0 的话,无解
if (a[i][n] == 1)
return 2;
// 不然, k*x = 0; 五穷解方程
return 1;
}
}
// 除去以上两种情况,即是 有唯一解,把举证格式化成正对角线矩阵
for (int i = n - 1; i >= 0; i--)
{
for (int j = i + 1; j < n; j++)
{
// 下同:a[i][n] ^= a[i][j] & a[j][n]; a[j][n] 是 x[j] 的值
a[i][n] ^= a[i][j] & a[j][n];
// 将 a[i][j] 置为 0 的过程
// a[i][j] ^= a[i][j] * a[i+1][i+1]
// 所以 a[i][n] 也要这样操作,结果就是 a[i][n]
}
}
return 0;
}
}
17. 欧几里得求最大公约数
static long gcd(long a, long b)
{
/* 辗转相除法
求 a,b 的公约数 == 求 a,b+ka 的公约数
例:求公约数的数 公约数
12,18 6
12,18+12=30 6
30 18 6
12,18+24=42 6 总结可知:a,b 的公约数 == a,b%a 的公约数
*/
return b != 0 ? gcd(b, a % b) : a;
}
18. 博弈论
① 台阶Nim游戏
🤠 参考地址
import java.util.*;
public class 台阶Nim游戏
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int res = 0;
for (int i = 1; i <= n; i++)//i 枚举台阶数,只考虑 奇数台阶
{
int x = sc.nextInt();
if (i % 2 != 0)
{
res ^= x;
}
}
if (res == 0)
{
System.out.println("No");
} else
{
System.out.println("Yes");
}
}
}
② 拆分Nim游戏
🤠 参考地址
import java.util.*;
public class 拆分Nim游戏
{
static int N = 110, n;
static int[] f = new int[N];
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int res = 0;
Arrays.fill(f, -1);
for (int i = 0; i < n; i++)
{
res ^= sg(sc.nextInt());
}
System.out.println(res == 0 ? "No" : "Yes");
}
static int sg(int x)
{
if (f[x] != -1)
{
return f[x];
}
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < x; i++)// i 表示分出的 新堆① 的石子数
for (int j = 0; j <= i; j++)// j 表示 新堆② 的石子数
{
// 公式 sg(i,j)=sg(i)^sg(j)
// 由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和
set.add(sg(i) ^ sg(j));
}
// 计算 mex
for (int i = 0;; i++)
{
if (!set.contains(i))
{
return f[x] = i;
}
}
}
}
19. 贪心
🤠 最长的上升子序列Ⅱ
🤠 f[i] 存的是最长子序列长度为 i 的 所有序列中末尾元素的 最小值
import java.io.*;
public class 最长上升子序列2
{
static int N = 100010;
static int[] a = new int[N];// 原数组
static int[] f = new int[N];// f[cnt] 存的是 有cnt长度的子序列的所有数 中的最小值, f 是单调递增的
static int cnt = 0;// cnt 表示最长子序列的长度
static int find(int x)
{
int l = 1;
int r = cnt;
while (l < r)
{
int mid = l + r >> 1;
if (f[mid] >= x)
r = mid;
else
l = mid + 1;
}
return l;
}
public static void main(String[] args) throws IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] split = in.readLine().split(" ");
int n = Integer.parseInt(split[0]);
String[] split2 = in.readLine().split(" ");
for (int i = 1; i <= n; i++)
{
a[i] = Integer.parseInt(split2[i - 1]);
}
// 只有一个元素时,最长序列肯定是 1
f[++cnt] = a[1];
for (int i = 2; i <= n; i++)
{
// 后边的元素 大于当前最大子序列的最小末尾值 的时候,子序列长度+1,新元素作为f[cnt++]的最小值
if (a[i] > f[cnt])
f[++cnt] = a[i];
else
{
// 通过二分查找到大于 a[i] 的最小 f[tem] ,即 在 f 中 tem 前边的数都小于 f[tem], 但f[tem-1] < a[i] < f[tem]
int tem = find(a[i]);
f[tem] = a[i];// 把 f[tem] 更新成 a[i],同样长度的子序列,末尾值取最小
}
}
System.out.println(cnt);
}
}
20. 递归
🤠 原题地址
🤠 类似于语法树,写了个破栈只过了一半案例😭
import java.io.*;
import java.util.*;
public class Main
{
static StringBuilder sb = new StringBuilder();
static String[] t;
static int flag = 1, i = 0;
// 正常情况,每 dfs 一次, i 就 +1 ,当恰好 i == t.length - 1 时退出循环,则说明 输入 的数据不多不少刚好拼成一个有效字符串
static void dfs()
{
if (flag == 0)
return;
if (i < t.length)
{
String str = t[i++];
sb.append(str);
if (str.equals("pair"))
{
sb.append("<");
dfs();
sb.append(",");
dfs();
sb.append(">");
}
} else
{
flag = 0;
}
}
public static void main(String[] args) throws IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(in.readLine());
t = in.readLine().split(" ");
dfs();
// 没有递归,int 过多的情况
if (i < t.length)
flag = 0;
if (flag == 1)
System.out.println(sb);
else
{
System.out.println("Error occurred");
}
}
}
21. 动态规划
① 最短编辑距离
🤠 参考地址
🤠 f [ i ][ j ]:表示 a[ 1~i ] 变成 b[ 1~j ] 的所有操作次数的最小值
import java.util.*;
public class 最短编辑距离
{
static int N = 1010;
static int[][] f = new int[N][N];
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String s1 = sc.next();
char[] a = s1.toCharArray();
int m = sc.nextInt();
String s2 = sc.next();
char[] b = s2.toCharArray();
for (int i = 0; i < m; i++)
f[0][i] = 1;
for (int i = 0; i < n; i++)
f[i][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 2; j <= m; j++)
{
// f[i-1][j]:i-1位与 j 位匹配成功,f[i][j-1]:i位与 j-1 位匹配成功
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);// min(删,增)
// 改,同则不用改
if (a[i - 1] == b[j - 1])
{
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
} else
{
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
}
}
System.out.println(f[n][m]);
}
}
② 编辑距离
🤠 参考地址
import java.io.*;
import java.util.*;
public class 编辑距离
{
static int N = 1010, n, m;
static int[][] f = new int[N][N];
static String[] sa = new String[N];
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws NumberFormatException, IOException
{
String[] split = in.readLine().split(" ");
n = Integer.parseInt(split[0]);
m = Integer.parseInt(split[1]);
for (int i = 0; i < n; i++)
sa[i] = in.readLine();
for (int i = 0; i < m; i++)
{
String[] split2 = in.readLine().split(" ");
String s = split2[0];
int limit = Integer.parseInt(split2[1]);
int ans = 0;
for (int j = 0; j < n; j++)
{
if (limit >= getEdits(sa[j], s))
ans++;
}
out.write(ans+"/n");
}
out.flush();
}
/**
* @param a 起始串
* @param b 目标串
*/
private static int getEdits(String a, String b)
{
for (int i = 0; i <= a.length(); i++)
f[i][0] = i;
for (int i = 0; i <= b.length(); i++)
f[0][i] = i;
for (int i = 1; i <= a.length(); i++)
for (int j = 1; j <= b.length(); j++)
{
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1); // min(删,增)
// 改
if (a.charAt(i - 1) == b.charAt(j - 1))
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
else
{
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
}
}
return f[a.length()][b.length()];
}
}
③ 整数划分
🤠 类似完全背包
🤠 f[ i ] [ j ]:表示 在(0~i)中的数选出总值为 j 的方案个数,支持重复选
🤠 参考地址
🤠 二维版
import java.util.Scanner;
public class 整数划分
{
static int N = 1010, mod = (int) 1e9 + 7;
static int[][] f = new int[N][N];
public static void main(String[] args)
{
int n = new Scanner(System.in).nextInt();
// 容量为 0 时,只有 都不选 一种方案
for (int i = 0; i <= n; i++)
f[i][0] = 1;
for (int i = 1; i <= n; i++)// 枚举在 i 个数中选
for (int j = 1; j <= n; j++)// j 枚举总和
{
f[i][j] = f[i - 1][j];// 处理 i > j 的情况
if (j >= i)
f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
}
System.out.println(f[n][n]);
}
}
🤠 一维优化版
import java.util.Scanner;
public class 整数划分plus
{
static int N = 1010, mod = (int) 1e9 + 7;
static int[] f = new int[N];// 枚举容量
public static void main(String[] args)
{
int n = new Scanner(System.in).nextInt();
f[0] = 1;
for (int i = 1; i <= n; i++)// 枚举在 i 个数中选
for (int j = i; j <= n; j++)// j 枚举总和,注意 j 从 i 开始
f[j] = (f[j] + f[j - i]) % mod;
System.out.println(f[n]);
}
}
🤠 刁钻角度版
🤠 状态表示: f[ i ][ j ] 所有总和是i,并且恰好可以表示成j个数的和的方案
import java.util.Scanner;
public class 整数划分2
{
static int N = 1010, mod = (int) 1e9 + 7;
static int[][] f = new int[N][N];// f[总和][可选数]
public static void main(String[] args)
{
int n = new Scanner(System.in).nextInt();
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
// 分为包含 1 的方案和不包含1的方案
f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
int res = 0;
for (int i = 0; i <= n; i++)
res += f[n][i];
System.out.println(res);
}
}