文章目录
一、单链表
1. 单链表(数组模拟)
static final int N = 100010;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
static int head, idx;
static int[] e = new int[N], ne = new int[N];
// 初始化
static void init() {
head = -1;
idx = 0;
}
// 将x插到头结点
static void add_to_head(int x) {
e[idx] = x;
ne[idx] = head;
head = idx++;
}
// 将x插到下标是k的点后面
static void add(int k, int x) {
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx++;
}
// 将下标是k的点后面的点删掉
static void remove(int k) {
ne[k] = ne[ne[k]];
}
//遍历
static void print(){
for (int i = head; i != -1; i = ne[i]) {
System.out.print(e[i] + " ");
}
}
二、双链表
1.双链表(数组模拟)
static final int N = 100010;
// e[i] 表示节点i的值
// l[i] 表示节点i的左指针是多少
// r[i] 表示节点i的右指针是多少
// idx 存储当前已经用到了哪个点
static int idx;
static int[] e = new int[N], l = new int[N], r = new int[N];
static void init() {
// 初始化:0是左端点;1是右端点
r[0] = 1;
l[1] = 0;
idx = 2;
}
// 在k的右边插入一个数x
static void insert(int k, int x) {
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx++;
}
// 删除第k个点
static void remove(int k) {
r[l[k]] = r[k];
l[r[k]] = l[k];
}
//遍历
static void print(){
for (int i = r[0]; i != 1; i = r[i]) {
System.out.print(e[i] + " ");
}
}
三、栈
1.表达式求值
示例:210-1000+24-(5×3)+(3/2)
注意:这里的表达式是中序遍历结果,区别于后序的不同做法
import java.util.*;
public class Main {
// 存放数字
static Stack<Integer> num = new Stack<>();
// 存放操作符
static Stack<Character> op = new Stack<>();
// 进行运算
static void eval() {
int b = num.pop();
int a = num.pop();
char c = op.pop();
int x;
if (c == '+')
x = a + b;
else if (c == '-')
x = a - b;
else if (c == '*')
x = a * b;
else
x = a / b;
num.push(x);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = in.nextLine();
// 设置操作符优先级
Map<Character, Integer> pr = new HashMap<>();
pr.put('-', 1);
pr.put('+', 1);
pr.put('*', 2);
pr.put('/', 2);
char[] chs = str.toCharArray();
for (int i = 0; i < chs.length; i++) {
if (Character.isDigit(chs[i])) {
int x = 0, j = i;
while (j < chs.length && Character.isDigit(chs[j])) {
x = x * 10 + chs[j++] - '0';
}
i = j - 1;
num.push(x);
} else if (chs[i] == '(') {
op.push('(');
} else if (chs[i] == ')') {
while (op.peek() != '(')
eval();
op.pop();
} else {
while (!op.isEmpty() && op.peek() != '(' && pr.get(op.peek()) >= pr.get(chs[i])) {
eval();
}
op.push(chs[i]);
}
}
while (!op.isEmpty()) {
eval();
}
System.out.println(num.peek());
}
}
四、单调栈
单调栈指栈中的元素单调递增或递减,主要用于找到每一个元素左边或者右边,第一个比它大或小的数
1. 单调栈
【单调栈】:给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1
import java.util.*;
public class Main {
static final int N = 100010;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] stk = new int[N];
int tt = 0;
for (int i = 0; i < n; i++) {
int x = in.nextInt();
while (tt > 0 && stk[tt] >= x) {
tt--;
}
if (tt > 0) {
System.out.print(stk[tt] + " ");
} else {
System.out.print(-1 + " ");
}
stk[++tt] = x;
}
}
}
2.仰视奶牛
【仰视奶牛】:输出每一个奶牛右边身高比他更高的最近奶牛位置
import java.util.*;
public class Main {
static final int N = 100010;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] h = new int[N];//记录身高
int[] res=new int[N];//记录结果
for(int i=1;i<=n;i++){
h[i]=in.nextInt();
}
//单调栈
Stack<Integer> st=new Stack<>();
for(int i=n;i>0;i--){
while(!st.isEmpty()&&h[st.peek()]<=h[i])st.pop();
if(!st.isEmpty()){
res[i]=st.peek();
}
st.push(i);
}
for(int i=1;i<=n;i++){
System.out.println(res[i]);
}
}
}
五、单调队列
单增或单减的队列
1. 滑动窗口
【滑动窗口】:给定一个长度为n的数组,大小为k的滑动窗口,输入每个滑动窗口中的最小值
import java.util.*;
public class Main {
static final int N = 1000010;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int k = in.nextInt();
int[] a = new int[N];// 原数组
for (int i = 0; i < n; i++) {
a[i] = in.nextInt();
}
Deque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
//当对头不在区间中时
if (!q.isEmpty() && i - k + 1 > q.peekFirst())
q.pollFirst();
//当队尾元素比当前元素大时,去除冗余
while (!q.isEmpty() && a[q.peekLast()] >= a[i])
q.pollLast();
q.offerLast(i);
if (i >= k - 1) {
System.out.print(a[q.peekFirst()] + " ");
}
}
}
}
六、KMP
时间复杂度:O(n+m)
1.字符串匹配
【字符串匹配】:求出 字符串s2 在 字符串s1 中所有出现的位置
import java.util.*;
public class Main {
static final int N = 1000010;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = in.nextLine();
int n = str.length();
//匹配串s1
char[] p = new char[N];
//匹配子串s2
char[] s = new char[N];
for (int i = 0; i < n; i++) {
s[i + 1] = str.charAt(i);
}
str = in.nextLine();
int m = str.length();
for (int i = 0; i < m; i++) {
p[i + 1] = str.charAt(i);
}
//求前缀匹配数组ne[],即处理匹配子串向前回退的位置
int[] ne = new int[N];
for (int i = 2, j = 0; i <= m; i++) {
while (j > 0 && 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 > 0 && s[i] != p[j + 1]) {
j = ne[j];
}
if (s[i] == p[j + 1]) {
j++;
}
if (j == m) {
System.out.println(i - m + 1);
j = ne[j];
}
}
for (int i = 1; i <= m; i++) {
System.out.print(ne[i] + " ");
}
}
}
七、Trie
快速存储和查找字符串集合的数据结构
1.字符串统计
【Trie字符串统计】
import java.util.*;
public class Main {
static final int N = 100010;
//idx、son存储单词
static int idx = 0;
static int[][] son = new int[N][26];
//作为每个单词结尾的记录标识
static int[] cnt = new int[N];
static void insert(char[] str) {
int p = 0;
for (int i = 0; i < str.length; i++) {
int u = str[i] - 'a';
//如果这个的儿子分支没有字符,说明这条分支还没有这个字符插入过
//就新建一个然后赋值为然后把【idx】下标赋值上去,作为每个分支的专属坐标
if (son[p][u] == 0)
son[p][u] = ++idx;//创建一个新节点
//p向下进一层
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)
return 0;
p = son[p][u];
}
return cnt[p];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
in.nextLine();
while (n-- > 0) {
String[] str = in.nextLine().split(" ");
if (str[0].equals("I")) {
insert(str[1].toCharArray());
} else {
System.out.println(query(str[1].toCharArray()));
}
}
}
}
2.最大异或对
【最大异或对】:给定的n个数字中找出得到的异或结果的最大值
import java.util.*;
public class Main {
static final int N = 3100010;
// idx、son存储
static int idx = 0;
static int[][] son = new int[N][2];
static void insert(int x) {
int p = 0;
//每个数的二进制有31位组成,从左开始遍历
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1;
if (son[p][u] == 0)
son[p][u] = ++idx;//这一层为空,创建结点
p = son[p][u];
}
}
static int query(int x) {
int p = 0, res = 0;
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1;
if (son[p][1 - u] != 0) {//优先考虑异位位置
res += (1 << i);
p = son[p][1 - u];
} else {
p = son[p][u];
}
}
return res;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
in.nextLine();
String[] strs = in.nextLine().split(" ");
for (int i = 0; i < strs.length; i++) {
insert(Integer.parseInt(strs[i]));
}
int res = 0;
for (int i = 0; i < n; i++) {
res = Math.max(res, query(Integer.parseInt(strs[i])));
}
System.out.println(res);
}
}
八、并查集
1.并查集是一种树形的数据结构,用于处理一些不相交集合的合并和查询问题
2.并查集的常用操作:
- 查找:查询两个元素是否在同一个集合中
- 合并:把两个不相交的集合合并为一个集合
//核心代码
// 寻找根节点祖先+路径压缩(每个数直接指向集合的根节点)
static int find(int x) {
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
1.合并集合
import java.util.*;
public class Main {
static final int N = 100010;
static int[] p = 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) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();// n个数
int m = in.nextInt();// m个操作
for (int i = 1; i <= n; i++) {
p[i] = i;// if(p[i]==i)表示树根,即为父节点
}
while (m-- > 0) {
String str = in.next();
int a = in.nextInt();
int b = in.nextInt();
// 合并集合
if (str.equals("M")) {
p[find(a)] = find(b);
} else {
// 查找
if (find(a) == find(b)) {
System.out.println("Yes");
} else {
System.out.println("No");
}
}
}
}
}
2.连通块中点的数量
import java.util.*;
public class Main {
static final int N = 100010;
// size存储每个集合中数的个数
static int[] p = new int[N], size = 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) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();// n个数
int m = in.nextInt();// m个操作
for (int i = 1; i <= n; i++) {
p[i] = i;// if(p[i]==i)表示树根,即为父节点
size[i] = 1;
}
while (m-- > 0) {
String str = in.next();
int a = in.nextInt();
// 合并集合
if (str.equals("C")) {
int b = in.nextInt();
if (find(a) == find(b))
continue;
else {
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
}
} else if (str.equals("Q1")) {
// 查找
int b = in.nextInt();
if (find(a) == find(b)) {
System.out.println("Yes");
} else {
System.out.println("No");
}
} else {
System.out.println(size[find(a)]);
}
}
}
}
九、堆
堆是一棵完全二叉树结构,有小根堆(根值小于左右儿子)和大根堆(根值大于左右儿子)
1.堆排序
import java.util.*;
public class Main {
static final int N = 100010;
static int[] h = new int[N];
static int size;
static void swap(int x, int y) {
int t = h[x];
h[x] = h[y];
h[y] = t;
}
//关键代码
static void down(int u) {
int t = u;
if (u * 2 <= size && h[u * 2] < h[t])
t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t])
t = u * 2 + 1;
if (u != t) {
swap(u, t);
down(t);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = in.nextInt();
}
size = n;
//从最后一个非叶子结点开始下沉
for (int i = n / 2; i > 0; i--) {
down(i);
}
while (m-- > 0) {
System.out.print(h[1] + " ");
h[1] = h[size--];
down(1);
}
}
}