当前位置: 首页 > news >正文

【算法训练】单向链表

单向链表

  • 题目描述
  • 步骤:
    • 法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:双向链表

使用哈希表维护前驱和后继节点以实现高效操作:

  1. 使用两个HashMap保存前驱和后继。
  2. 初始化元素1的前驱和后继为0。
  3. 读取每个操作,并根据类型处理:
  • 插入操作:更新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。
  1. 处理输入输出高效。

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;
}

相关文章:

  • pandas中新增的case_when()方法
  • c++ 命名空间 namespace
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的数据验证:使用 Hibernate Validator
  • 数据建模流程: 概念模型>>逻辑模型>>物理模型
  • NSSCTF(MISC)——[NSSRound#4 SWPU]Type Message
  • 网络爬虫-2:基础与理论
  • 论文阅读笔记:Denoising Diffusion Probabilistic Models (3)
  • C语言中*a与a的区别和联系
  • 数据结构——B树、B+树、哈夫曼树
  • 安全测试理论
  • JavaScript 性能优化实战
  • 【云馨AI-大模型】自动化部署Dify 1.1.2,无需科学上网,Linux环境轻松实现,附Docker离线安装等
  • 【C++教程】setw()函数的使用方法
  • 深入理解Linux中的SCP命令:使用与原理
  • Hutool中的相关类型转换
  • 山东大学数据结构课程设计
  • linux--时区查看和修改
  • 动态规划-01背包
  • 牛客网【模板】二维差分(详解)c++
  • 分区表的应用场景与优化实践
  • 老凤祥一季度净利减少两成,去年珠宝首饰营收下滑19%
  • 江苏银行一季度净赚近98亿增逾8%,不良贷款率微降
  • 国家发改委答澎湃:将建立和实施育儿补贴制度,深入实施提振消费专项行动
  • 2025厦门体育产业采风活动圆满举行
  • 南阳市委原书记朱是西被“双开”:搞劳民伤财的“政绩工程”
  • 江西省宁都县政协原二级调研员谢亦礼被查