【算法训练】单向链表
单向链表
- 题目描述
- 步骤:
- 法1:单向链表(建议)
- 法2:双向链表
- java
- 单向链表
- 双向链表
- 普通
- 缓冲流
- 注意:
- IOException
- java.io
- BufferedWriter
- getOrDefault
- c++
B3631 单向链表
题目描述
实现一个数据结构,维护一张表(最初只有一个元素 1)。需要支持下面的操作,其中 x 和 y 都是 1 到 10 6
范围内的正整数,且保证任何时间表中所有数字均不相同,操作数量不多于 10 5:
- 1 x y :将元素 y 插入到 x 后面;
- 2 x :询问 x 后面的元素是什么。如果 x 是最后一个元素,则输出 0;
- 3 x:从表中删除元素 x 后面的那个元素,不改变其他元素的先后顺序。
输入格式
第一行一个整数 q 表示操作次数。
接下来 q 行,每行表示一次操作,操作具体见题目描述。
输出格式
对于每个操作 2,输出一个数字,用换行隔开。
输入输出样例
输入
6
1 1 99
1 99 50
1 99 75
2 99
3 75
2 1
输出
75
99
步骤:
这个数据结构需要高效地处理插入、查询和删除,尤其是操作次数可能达到1e5次,所以时间复杂度必须很低,最好是O(1)的操作。如果用普通的链表结构,比如LinkedList,插入和删除的时间复杂度是O(n),这样对于1e5次操作来说可能会超时。所以必须找一个更高效的方法。
这时候想到,可以用哈希表来模拟链表的指针。因为每个节点都是唯一的,所以可以用一个哈希表来存储每个节点的下一个节点。比如,用一个HashMap<Integer, Integer> next,其中next.get(x)返回x的下一个节点。这样,插入、查询和删除操作都可以在O(1)的时间内完成
法1:单向链表(建议)
初始化:初始时表中只有一个元素 1,其后继为 0。
插入操作(操作1):读取 x 和 y,将 y 插入到 x 之后。保存 x 的原后继节点,更新 x 的后继为 y,并将 y 的后继设为原后继节点。
查询操作(操作2):直接读取 x 的后继节点,若不存在则返回 0。
删除操作(操作3):读取 x,找到其后继节点 y 及其后继节点 z。将 x 的后继节点更新为 z,跳过 y。
这种方法通过哈希表实现了高效的节点访问和指针调整,确保每个操作的时间复杂度为 O(1),能够高效处理最多 10^5 次操作。
法2:双向链表
使用哈希表维护前驱和后继节点以实现高效操作:
- 使用两个HashMap保存前驱和后继。
- 初始化元素1的前驱和后继为0。
- 读取每个操作,并根据类型处理:
- 插入操作:更新x和y的前驱、后继,以及原x的后继的前驱。
当操作符op等于1时,表示要在元素x的后面插入元素y。代码的步骤如下:
1.读取输入的x和y。
2.获取x当前的后继节点originalNext,使用getOrDefault方法,如果x不存在于next哈希表中,默认值为0。
3.将x的后继设置为y,即next.put(x, y)。
4.将y的前驱设置为x,即prev.put(y, x)。
5.将y的后继设置为originalNext,即next.put(y, originalNext)。
6.如果originalNext不为0,说明x原来有后继节点,现在需要更新这个原后继节点的前驱为y,即prev.put(originalNext, y)。 - 查询操作:获取x的后继,输出或0。
- 删除操作:获取x的后继y,以及y的后继z,更新x的后继为z,z的前驱为x。
- 处理输入输出高效。
java
单向链表
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int q = sc.nextInt();
// 使用哈希表维护节点的后继关系
Map<Integer, Integer> next = new HashMap<>();
next.put(1, 0); // 初始只有元素1,后继为0
for (int i = 0; i < q; i++) {
int op = sc.nextInt();
if (op == 1) {
// 插入操作:将y插入到x后面
int x = sc.nextInt();
int y = sc.nextInt();
int originalNext = next.getOrDefault(x, 0);
next.put(x, y); // x的后继改为y
next.put(y, originalNext); // y的后继设为原x的后继
} else if (op == 2) {
// 查询操作:输出x的后继
int x = sc.nextInt();
System.out.println(next.getOrDefault(x, 0));
} else if (op == 3) {
// 删除操作:删除x的后继节点
int x = sc.nextInt();
int y = next.getOrDefault(x, 0);
if (y != 0) {
int z = next.getOrDefault(y, 0);
next.put(x, z); // x的后继直接指向y的后继
}
}
}
}
}
双向链表
普通
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int q = sc.nextInt();
// 用两个哈希表维护链表结构
Map<Integer, Integer> prev = new HashMap<>();
Map<Integer, Integer> next = new HashMap<>();
// 初始化元素1
prev.put(1, 0);
next.put(1, 0);
for (int i = 0; i < q; i++) {
int op = sc.nextInt();
if (op == 1) {
// 插入操作:x后面插入y
int x = sc.nextInt();
int y = sc.nextInt();
int originalNext = next.getOrDefault(x, 0);
next.put(x, y);
prev.put(y, x);
next.put(y, originalNext);
if (originalNext != 0) {
prev.put(originalNext, y);
}
} else if (op == 2) {
// 查询操作:x的后继
int x = sc.nextInt();
int res = next.getOrDefault(x, 0);
System.out.println(res == 0 ? 0 : res);
} else if (op == 3) {
// 删除操作:删除x的后继
int x = sc.nextInt();
int y = next.getOrDefault(x, 0);
if (y != 0) {
int z = next.getOrDefault(y, 0);
next.put(x, z);
if (z != 0) {
prev.put(z, x);
}
// 清理被删除节点的关系
next.remove(y);
prev.remove(y);
}
}
}
}
}
缓冲流
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 使用两个哈希表维护前驱和后继关系
HashMap<Integer, Integer> prev = new HashMap<>();
HashMap<Integer, Integer> next = new HashMap<>();
// 初始化元素1
prev.put(1, 0);
next.put(1, 0);
int q = Integer.parseInt(br.readLine());
List<String> output = new ArrayList<>();
while (q-- > 0) {
String[] parts = br.readLine().split(" ");
int op = Integer.parseInt(parts[0]);
if (op == 1) {
// 插入操作:x后面插入y
int x = Integer.parseInt(parts[1]);
int y = Integer.parseInt(parts[2]);
int originalNext = next.getOrDefault(x, 0);
next.put(x, y);
prev.put(y, x);
next.put(y, originalNext);
if (originalNext != 0) {
prev.put(originalNext, y);
}
} else {
int x = Integer.parseInt(parts[1]);
if (op == 2) {
// 查询操作:x的后继元素
int res = next.getOrDefault(x, 0);
output.add(res == 0 ? "0" : Integer.toString(res));
} else if (op == 3) {
// 删除操作:删除x后面的元素
int y = next.getOrDefault(x, 0);
if (y != 0) {
int z = next.getOrDefault(y, 0);
next.put(x, z);
if (z != 0) {
prev.put(z, x);
}
// 清理被删除节点的关系(可选)
next.remove(y);
prev.remove(y);
}
}
}
}
// 输出所有结果
bw.write(String.join("\n", output));
bw.flush();
}
}
注意:
IOException
java.io
BufferedWriter
当需要处理大量输出时(如算法题),建议使用 BufferedWriter:
// 高效输出配置
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 输出内容并换行
bw.write("Hello World");
bw.newLine();
// 批量刷新输出(减少 I/O 次数)
bw.flush();
getOrDefault
HashMap 的 getOrDefault 方法用于安全地获取指定键对应的值,若键不存在则返回预设的默认值。以下是分步说明和使用示例:
c++
#include<bits/stdc++.h>
using namespace std;
list<int>::iterator pos[1000005];
int main()
{
list<int>l;
int n;
pos[1]=l.insert(l.end(),1);
cin>>n;
for (int i=0;i<n;++i)
{
int a;
cin>>a;
if (a==1)
{
int x,y;
cin>>x>>y;
list<int>::iterator temp=pos[x];
pos[y]=l.insert(++temp,y);
}
else
{
int x;
cin>>x;
if (a==2)
{
list<int>::iterator temp=pos[x];
if (++temp!=l.end())
{
cout<<*temp<<endl;
}
else cout<<0<<endl;
}
if (a==3)
{
list<int>::iterator temp=pos[x];
if (++temp!=l.end())
l.erase(temp);
}
}
}
return 0;
}