LeetCode题练习与总结:链表随机节点--382

server/2024/11/18 7:19:36/

一、题目描述

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。

实现 Solution 类:

  • Solution(ListNode head) 使用整数数组初始化对象。
  • int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。

示例:

输入
["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
[[[1, 2, 3]], [], [], [], [], []]
输出
[null, 1, 3, 2, 2, 3]解释
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // 返回 1
solution.getRandom(); // 返回 3
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 3
// getRandom() 方法应随机返回 1、2、3中的一个,每个元素被返回的概率相等。

提示:

  • 链表中的节点数在范围 [1, 10^4] 内
  • -10^4 <= Node.val <= 10^4
  • 至多调用 getRandom 方法 10^4 次

二、解题思路

  1. 初始化:首先,我们需要一个ListNode类来表示链表的节点,并且需要实现一个deserialize方法来将输入的字符串转换为链表ListNode类包含一个整数值val和一个指向下一个节点的引用next

  2. 反序列化deserialize方法将输入的字符串(形如"[1,2,3]")转换成链表。这个方法首先创建一个哑节点dummy,然后遍历字符串中的每个值,创建新的ListNode并将其添加到链表中。最后,返回哑节点的下一个节点,即链表的头节点。

  3. Solution类Solution类负责实现随机选择链表节点的功能。它包含一个构造函数,接受链表的头节点,并初始化一个Random对象用于生成随机数。

  4. 随机选择节点getRandom方法是Solution类中的关键方法。它遍历链表,使用水塘抽样算法(Reservoir Sampling)来随机选择一个节点。在遍历过程中,每个节点都有相同的概率被选中。具体来说,遍历到第i个节点时,我们以1/i的概率决定是否选择当前节点。如果决定选择当前节点,我们就更新结果result为当前节点的值。

  5. 主方法:在Main类的main方法中,我们首先使用ListNode.deserialize方法将字符串转换为链表。然后,我们创建一个Solution对象,并多次调用getRandom方法来模拟随机选择节点值的过程。

三、具体代码

import java.util.Random;class ListNode {int val;ListNode next;ListNode(int x) { val = x; }// 反序列化方法,将字符串转换为链表public static ListNode deserialize(String data) {if (data.isEmpty()) return null;String[] values = data.substring(1, data.length() - 1).split(",");ListNode dummy = new ListNode(0);ListNode current = dummy;for (String value : values) {current.next = new ListNode(Integer.parseInt(value));current = current.next;}return dummy.next;}
}class Solution {ListNode head;Random random;public Solution(ListNode head) {this.head = head;this.random = new Random();}public int getRandom() {ListNode current = head;int result = head.val;int i = 1;while (current != null) {if (random.nextInt(i) == 0) {result = current.val;}current = current.next;i++;}return result;}
}public class Main {public static void main(String[] args) {// 反序列化链表ListNode head = ListNode.deserialize("[1,2,3]");// 创建Solution对象Solution solution = new Solution(head);// 模拟多次调用getRandom方法System.out.println(solution.getRandom());System.out.println(solution.getRandom());System.out.println(solution.getRandom());System.out.println(solution.getRandom());System.out.println(solution.getRandom());}
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • ListNode.deserialize 方法:

    • 时间复杂度:O(n),其中 n 是输入字符串中数字的数量。这个方法遍历了字符串中的所有数字,每个数字都进行了一次解析操作。
  • Solution.getRandom 方法:

    • 时间复杂度:O(m),其中 m 是链表的长度。每次调用 getRandom 方法都需要遍历整个链表
  • Main.main 方法:

    • 时间复杂度:O(1) + O(m) + O(k * m),其中 m 是链表的长度,k 是调用 getRandom 方法的次数。ListNode.deserialize 方法调用一次,所以是 O(1),然后创建 Solution 对象也是 O(1),接下来调用 getRandom 方法 k 次,每次是 O(m)。

综合上述分析,整体时间复杂度是:

  • 反序列化链表:O(n)
  • 创建 Solution 对象:O(1)
  • 调用 getRandom 方法 k 次:O(k * m)

如果 n 是链表的长度,那么反序列化链表的时间复杂度可以视为 O(m),因此整体时间复杂度是 O(m + k * m) = O((k + 1) * m)。

2. 空间复杂度
  • ListNode.deserialize 方法:

    • 空间复杂度:O(m),其中 m 是链表的长度。这个方法创建了 m 个 ListNode 对象。
  • Solution 类:

    • 空间复杂度:O(1),Solution 类只存储了链表的头节点和一个 Random 对象。
  • Main.main 方法:

    • 空间复杂度:O(1),除了链表本身外,main 方法没有使用额外的空间。

综合上述分析,整体空间复杂度是:

  • 反序列化链表:O(m)
  • 创建 Solution 对象:O(1)
  • 调用 getRandom 方法:O(1)

因此,整体空间复杂度是 O(m),即链表长度。

五、总结知识点

  • Java 类定义

    • ListNode 和 Solution 类的定义,以及它们各自的构造方法。
  • 链表操作

    • 链表的创建和遍历。
    • 使用 ListNode 类创建链表节点,并链接它们形成链表
  • 字符串处理

    • 使用 substring 方法截取字符串。
    • 使用 split 方法按特定字符分割字符串。
  • 数字解析

    • 使用 Integer.parseInt 方法将字符串转换为整数。
  • 动态内存分配

    • 在 deserialize 方法中动态创建链表节点。
  • 伪随机数生成

    • 使用 Random 类生成伪随机数。
  • 概率算法

    • 在 getRandom 方法中,使用了一个著名的概率算法,称为“水塘抽样”(Reservoir Sampling),以等概率从链表中随机选择一个节点。
  • 循环和递增

    • 在 getRandom 方法中使用 while 循环遍历链表,并使用变量 i 来记录遍历的节点数量。
  • 设计模式

    • 使用了“哑节点”(dummy node)设计模式在 deserialize 方法中简化链表节点的添加操作。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。


http://www.ppmy.cn/server/142845.html

相关文章

Electron 沙盒模式与预加载脚本:保障桌面应用安全的关键机制

在 Electron 中&#xff0c;沙盒&#xff08;Sandbox&#xff09; 和 预加载脚本&#xff08;Preload&#xff09; 是关键的安全机制和架构概念。它们一起用于确保应用的安全性和稳定性&#xff0c;特别是当需要在渲染进程中访问某些系统资源时。以下是对沙盒模式和预加载脚本的…

java集合—List的底层结构和源码分析

Java集合框架中的List接口是一个有序的集合&#xff0c;它可以存储重复的元素。List接口的底层结构可以有多种实现&#xff0c;常见的有ArrayList和LinkedList。 ArrayList的底层结构&#xff1a; ArrayList是基于数组实现的&#xff0c;其内部使用一个Object类型的数组来存储…

ASP.NET 部署到IIS,访问其它服务器的共享文件 密码设定

asp.net 修改上面的 IIS需要在 配置文件 添加如下内容 》》》web.config <system.web><!--<identity impersonate"true"/>--><identity impersonate"true" userName"您的账号" password"您的密码" /><co…

Python 正则表达式进阶用法:分组与引用详解

Python 正则表达式进阶用法&#xff1a;分组与引用详解 正则表达式是一种用于字符串匹配和处理的强大工具。它不仅能识别简单的文本模式&#xff0c;还能通过更高级的特性来完成复杂的文本处理任务。本文将深入探讨 Python 正则表达式中的“分组”和“引用”——两个在高级匹配…

单片机学习笔记 2. LED灯闪烁

目录 0、实现的功能 1、Keil工程 2、代码实现 0、实现的功能 LED灯闪烁 1、Keil工程 闪烁原理&#xff1a;需要进行软件延时达到人眼能分辨出来的效果。常用的延时方法有软件延时和定时器延时。此次先进行软件延时 具体操作步骤和之前的笔记一致。此次主要利用无符号整型的范…

设计一个设备探测1pv

探测**1 pV&#xff08;皮伏特&#xff0c;&#xff09;的微弱电信号是一个非常具有挑战性但可行的目标。这种极低电压的探测需要超高灵敏度的电路设计和信号处理技术&#xff0c;同时要尽量抑制噪声对信号的干扰。 以下是设计此类设备的一些核心思路和技术方向&#xff1a; …

大数据新视界 -- 大数据大厂之 Impala 性能优化:集群资源动态分配的智慧(上)(23 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

K8s学习笔记之了解k8s的网络模型

文章目录 docker 网络模型容器与容器之间&#xff0c;容器与宿主机之间如何通信容器访问外部网络外部网络访问容器 k8s 网络模型CNIpod 网络配置流程 k8s 热门网络插件介绍Flannel 来源Calico 来源Cilium 来源 k8s 网络插件的工作模式Flannel 的工作模式Calico 的工作模式BGP 和…