203. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
这段代码是用Java编写的,它定义了一个名为Solution
的类,其中包含一个方法removeElements
。这个方法用于从一个链表中移除所有值为val
的节点。链表节点的定义(尽管没有直接展示,但根据上下文可以推断)通常如下:
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
现在让我们详细解析removeElements
方法的工作原理:
方法签名
public ListNode removeElements(ListNode head, int val)
- ListNode head: 这是链表的头节点。
- int val: 要从链表中移除的元素值。
方法逻辑
-
基本情况检查:
if (head == null) { return head; }
如果链表为空(头节点为null),则直接返回null,无需处理。
-
递归移除后续节点中的目标值:
head.next = removeElements(head.next, val);
这行代码递归地对链表的下一个节点调用
removeElements
方法,目的是先处理链表的剩余部分,逐步向上传递处理后的链表头。 -
处理当前节点:
return head.val == val ? head.next : head;
- 如果当前节点的值
head.val
等于要移除的值val
,则返回head.next
,实际上就是跳过了当前节点,实现了移除操作。 - 如果当前节点的值不等于
val
,则保持当前节点不变,直接返回head
。
- 如果当前节点的值
递归过程解释
这个方法采用递归的方式,从链表的末尾开始向前处理每个节点,通过修改指针实现节点的移除(而非真正意义上的“删除”)。当递归调用返回到最上层时,整个链表中所有值为val
的节点都被“跳跃”过去,从而实现了从链表中移除这些节点的目的。
这种方法的优点是逻辑简洁,递归天然处理了链表的遍历。但需要注意,如果链表非常长,可能会导致较大的栈空间消耗。
为了将 removeElements
方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并且定义 ListNode
类。以下是修改后的代码:
import java.util.Scanner;
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取链表的元素个数
int n = scanner.nextInt();
// 读取链表的元素
ListNode dummy = new ListNode(0);
ListNode current = dummy;
for (int i = 0; i < n; i++) {
current.next = new ListNode(scanner.nextInt());
current = current.next;
}
// 读取要移除的元素的值
int val = scanner.nextInt();
// 创建Solution对象并调用removeElements方法
Solution solution = new Solution();
ListNode result = solution.removeElements(dummy.next, val);
// 输出结果链表
current = result;
while (current != null) {
System.out.print(current.val + " ");
current = current.next;
}
System.out.println();
}
}
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
说明
-
输入格式:
- 第一行输入一个整数
n
,表示链表的元素个数。 - 接下来的一行输入
n
个整数,表示链表的元素。 - 最后一行输入一个整数
val
,表示要移除的元素的值。
- 第一行输入一个整数
-
输出格式:
- 输出链表中移除所有值为
val
的元素后的链表,每个元素之间用空格隔开。
- 输出链表中移除所有值为
使用示例
假设输入如下:
5
1 2 6 3 4
6
程序会输出:
1 2 3 4
因为链表 [1, 2, 6, 3, 4]
中移除所有值为 6
的元素后,结果链表为 [1, 2, 3, 4]
。
你可以将这段代码保存到一个文件中,比如 Main.java
,然后在命令行中使用 javac Main.java
进行编译,使用 java Main
运行,并在运行时通过标准输入提供数据。
707. 设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
index = Math.max(0, index);
size++;
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
这段代码定义了一个自定义的链表类MyLinkedList
,它是基于单链表结构实现的,其中包含了一些基本操作方法,如添加、删除、获取链表中的元素等。同时定义了一个内部类ListNode
,用于表示链表中的每一个节点。下面是这个代码的详细解释:
类定义
-
MyLinkedList:
size
:记录链表中的元素个数。head
:虚拟头节点,其值设为0,不存储实际数据,便于插入和删除操作的统一处理。
-
ListNode(内部类):
val
:节点存储的值。next
:指向下一个节点的引用。
方法说明
-
构造函数 MyLinkedList:
初始化链表,设置size
为0,创建一个值为0的虚拟头节点。 -
get(int index):
根据索引获取链表中的元素。若索引越界,返回-1;否则遍历至指定位置返回节点值。 -
addAtHead(int val):
在链表头部添加元素。调用addAtIndex(0, val)
实现。 -
addAtTail(int val):
在链表尾部添加元素。调用addAtIndex(size, val)
实现。 -
addAtIndex(int index, int val):
在指定位置插入元素。首先判断插入位置是否合法,然后更新链表大小,找到插入位置的前驱节点,创建新节点并插入链表中。 -
deleteAtIndex(int index):
删除指定位置的元素。检查索引合法性,然后减小链表大小,找到待删除节点的前驱节点,调整指针跳过待删除节点。
特点
- 使用虚拟头节点简化边界条件处理,尤其是在插入和删除操作中。
- 提供了基础的链表操作,包括查询、插入和删除,满足基本的数据结构需求。
- 通过索引进行操作,时间复杂度主要取决于索引值,平均情况下为O(n),其中n为链表长度。
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
方法一:迭代
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
这段代码是用Java编写的,它实现了一个名为Solution
的类,该类中有一个公共方法reverseList
。这个方法的功能是反转一个给定的单链表。链表的节点定义(尽管没有直接在代码中展示,但可以根据上下文推断)通常如下:
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
现在,让我们详细解析reverseList
方法的工作原理:
方法签名
public ListNode reverseList(ListNode head)
- ListNode head:这是链表的头节点。
方法逻辑
-
初始化指针:
ListNode prev = null; ListNode curr = head;
prev
初始化为null
,它将用于保存当前节点的前一个节点。curr
初始化为head
,即当前处理的节点。
-
循环反转链表:
while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; }
- 在当前节点
curr
不为null
的情况下执行循环。 next
临时存储curr
的下一个节点,因为在改变curr.next
之前需要保留这个信息。- 然后,将
curr
的next
指针指向前一个节点prev
,实现了单链表节点间的指向反转。 - 更新
prev
和curr
,使它们分别向前移动一位,继续处理下一个节点,直到链表末尾。
- 在当前节点
-
返回反转后的头节点:
return prev;
当循环结束时,
prev
指向了原链表的最后一个节点,因为原链表的头节点变成了新链表的尾节点,所以返回prev
作为新链表的头节点。
总结
这个方法通过一次遍历实现了链表的反转,时间复杂度为O(n),其中n是链表的长度。它只使用了常数级别的额外空间,因此空间复杂度为O(1)。这种方法简洁高效,是反转链表的经典解法之一。
为了将 reverseList
方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode
类。以下是修改后的代码:
import java.util.Scanner;
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取链表的元素个数
int n = scanner.nextInt();
// 创建链表并读取链表元素
ListNode dummy = new ListNode(0);
ListNode current = dummy;
for (int i = 0; i < n; i++) {
current.next = new ListNode(scanner.nextInt());
current = current.next;
}
// 创建Solution对象并调用reverseList方法
Solution solution = new Solution();
ListNode result = solution.reverseList(dummy.next);
// 输出结果链表
current = result;
while (current != null) {
System.out.print(current.val + " ");
current = current.next;
}
System.out.println();
}
}
说明
-
输入格式:
- 第一行输入一个整数
n
,表示链表的元素个数。 - 接下来的一行输入
n
个整数,表示链表的元素。
- 第一行输入一个整数
-
输出格式:
- 输出反转后的链表,每个元素之间用空格隔开。
使用示例
假设输入如下:
5
1 2 3 4 5
程序会输出:
5 4 3 2 1
因为链表 [1, 2, 3, 4, 5]
反转后,结果链表为 [5, 4, 3, 2, 1]
。
你可以将这段代码保存到一个文件中,比如 Main.java
,然后在命令行中使用 javac Main.java
进行编译,使用 java Main
运行,并在运行时通过标准输入提供数据。
方法二:递归
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
这段代码同样实现了链表的反转功能,不过采用了递归的方式。这段代码也是在Solution
类中定义了一个方法reverseList
,其参数为链表的头节点head
。下面是这个递归方法的详细解析:
基本情况(Base Case):
if (head == null || head.next == null) {
return head;
}
- 当链表为空(
head == null
)或者链表只有一个节点(head.next == null
,即没有next
节点)时,直接返回当前的head
,因为不需要反转。
递归步骤(Recursive Case):
ListNode newHead = reverseList(head.next);
- 对
head
的下一个节点head.next
进行递归调用reverseList
,这一步会递归地反转以head.next
为头节点的子链表,并返回反转后的新头节点,记为newHead
。
反转当前节点:
head.next.next = head;
head.next = null;
- 当递归返回后,将当前节点
head
的下一个节点的next
指针(即head.next.next
)指向当前节点head
,完成当前节点与其后继节点之间方向的反转。 - 然后将当前节点
head
的next
指针设为null
,断开与原后继节点的连接,这在递归过程中是必要的,因为每个节点在反转过程中都将变成尾节点。
返回结果:
return newHead;
- 最终返回递归调用后得到的新头节点
newHead
,当递归层层返回时,就构建出了完全反转的链表。
总结
此递归方法通过将问题分解为越来越小的子问题(每次处理链表的剩余部分)来实现链表的反转,直至达到基本情况。递归解法的时间复杂度同样是O(n),其中n为链表的长度,因为它每个节点都会被访问一次。空间复杂度也是O(n),因为在递归调用栈上会有n层深度,每层调用对应链表的一个节点。尽管递归解法在某些情况下可能不如迭代解法节省空间,但它提供了另一种理解和实现链表反转的优雅方式。
为了将 reverseList
方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode
类。以下是修改后的代码:
import java.util.Scanner;
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取链表的元素个数
int n = scanner.nextInt();
// 创建链表并读取链表元素
ListNode dummy = new ListNode(0);
ListNode current = dummy;
for (int i = 0; i < n; i++) {
current.next = new ListNode(scanner.nextInt());
current = current.next;
}
// 创建Solution对象并调用reverseList方法
Solution solution = new Solution();
ListNode result = solution.reverseList(dummy.next);
// 输出结果链表
current = result;
while (current != null) {
System.out.print(current.val + " ");
current = current.next;
}
System.out.println();
}
}
说明
-
输入格式:
- 第一行输入一个整数
n
,表示链表的元素个数。 - 接下来的一行输入
n
个整数,表示链表的元素。
- 第一行输入一个整数
-
输出格式:
- 输出反转后的链表,每个元素之间用空格隔开。
使用示例
假设输入如下:
5
1 2 3 4 5
程序会输出:
5 4 3 2 1
因为链表 [1, 2, 3, 4, 5]
反转后,结果链表为 [5, 4, 3, 2, 1]
。
你可以将这段代码保存到一个文件中,比如 Main.java
,然后在命令行中使用 javac Main.java
进行编译,使用 java Main
运行,并在运行时通过标准输入提供数据。