203. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
- 列表中的节点数目在范围
[0, 104]内 1 <= Node.val <= 500 <= val <= 50
虚拟头结点法:
虚拟头节点是一个指向原头节点的额外节点,这样可以统一处理头节点和非头节点的删除操作。
算法步骤
- 初始化虚拟头节点:创建一个虚拟头节点
dummyHead,它的next指向原头节点head。 - 遍历链表:初始化,使用一个指针
cur从dummyHead开始遍历链表。 - 删除操作:如果
cur.next存在且其值等于val,则删除cur.next节点,即将cur.next指向cur.next.next。否则,移动cur到下一个节点。 - 返回结果:遍历完成后,返回
dummyHead.next,即新链表的头节点。
代码如下:
// 定义链表节点类
class ListNode {
int val; // 节点的值
ListNode next; // 指向下一个节点的引用
ListNode(int x) { // 构造函数
val = x;
next = null;
}
}
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 创建一个虚拟头节点,这样可以简化头节点的删除操作
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead; // 使用cur指针遍历链表
// 遍历链表,直到cur节点为空
while (cur.next != null) {
// 检查cur.next节点的值是否等于val
if (cur.next.val == val) {
// 如果等于val,删除这个节点
ListNode toRemove = cur.next;
cur.next = cur.next.next; // 将cur的下一个节点指向要删除节点的下一个节点
toRemove = null; // 帮助垃圾收集器回收内存
} else {
// 如果不等于val,移动到下一个节点
cur = cur.next;
}
}
// 返回新链表的头节点,即dummyHead的下一个节点
return dummyHead.next;
}
public static void main(String[] args) {
Solution solution = new Solution();
// 创建链表 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(6);
head.next.next.next = new ListNode(3);
head.next.next.next.next = new ListNode(4);
head.next.next.next.next.next = new ListNode(5);
head.next.next.next.next.next.next = new ListNode(6);
// 目标值 val
int val = 6;
// 调用函数并打印结果
ListNode newHead = solution.removeElements(head, val);
printList(newHead); // 应该打印出 1 -> 2 -> 3 -> 4 -> 5
}
// 辅助函数,用于打印链表
public static void printList(ListNode node) {
while (node != null) {
System.out.print(node.val + " -> ");
node = node.next;
}
System.out.println("null");
}
}
思考
- 为什么使用虚拟头节点?
在不使用虚拟头结点的情况下,如果链表的头结点需要被删除,我们就需要特殊处理这种情况,因为头结点来帮助我们重新链接链表。而使用虚拟头结点之后,就可以统一处理所有结点的删除操作。无论是头结点还是其他结点,都可以看作是相似结点。
栗子
- 初始状态
链表:1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6
- 步骤 1: 初始化虚拟头节点
我们创建一个虚拟头节点 dummyHead,它的 next 指向真正的头节点 1。
dummyHead -> 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6
- 步骤 2: 遍历链表
使用一个指针 cur 从 dummyHead 开始遍历链表。
-
cur指向dummyHead,cur.next指向1。 -
cur.next.val不等于val,移动cur到1。 -
cur指向1,cur.next指向2。 -
cur.next.val不等于val,移动cur到2。 -
cur指向2,cur.next指向6。 -
cur.next.val等于val,删除6,cur.next指向3。 -
cur指向3,cur.next指向4。 -
cur.next.val不等于val,移动cur到4。 -
cur指向4,cur.next指向5。 -
cur.next.val不等于val,移动cur到5。 -
cur指向5,cur.next指向6。 -
cur.next.val等于val,删除6,cur.next指向null。 -
最终状态
链表:1 -> 2 -> 3 -> 4 -> 5
707. 设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
MyLinkedList()初始化MyLinkedList对象。int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1。void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。
示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
提示:
0 <= index, val <= 1000- 请不要使用内置的 LinkedList 库。
- 调用
get、addAtHead、addAtTail、addAtIndex和deleteAtIndex的次数不超过2000。
单链表法:每个节点包含两个属性:val(当前节点的值)和next(指向下一个节点的指针)
算法步骤
- 初始化:创建一个虚拟头节点
dummyHead,用于简化头节点的操作,同时记录链表的长度size。 - 获取节点值:通过遍历链表,检查给定的索引
index是否有效,如果有效,则返回对应节点的值,否则返回-1。 - 在头部添加节点:在虚拟头节点
dummyHead之后添加新节点,更新dummyHead.next。 - 在尾部添加节点:遍历链表找到最后一个节点,然后在其后添加新节点。
- 在指定位置添加节点:遍历链表找到指定位置的前一个节点,然后在其后添加新节点。
- 删除指定位置的节点:遍历链表找到指定位置的前一个节点,然后更新其
next指针以跳过要删除的节点,并释放被删除节点的内存。
代码如下:
// 定义链表节点类
class ListNode {
int val; // 节点存储的值
ListNode next; // 指向下一个节点的引用
// 构造函数,初始化节点的值和下一个节点
ListNode(int x) {
val = x;
next = null;
}
}
// 定义链表类
class MyLinkedList {
private ListNode dummyHead; // 虚拟头节点,用于简化头节点的操作
private int size; // 记录链表的长度
// 初始化链表
public MyLinkedList() {
dummyHead = new ListNode(0); // 创建一个值为0的虚拟头节点
size = 0; // 初始化链表长度为0
}
// 获取链表中第index个节点的值,如果索引无效,则返回-1
public int get(int index) {
if (index < 0 || index >= size) return -1; // 检查索引是否有效
ListNode cur = dummyHead.next; // 从虚拟头节点的下一个节点开始遍历
for (int i = 0; i < index; i++) {
cur = cur.next; // 移动到指定索引的节点
}
return cur.val; // 返回找到的节点的值
}
// 在链表头部添加一个新节点,值为val
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = dummyHead.next; // 新节点指向原来的第一个节点
dummyHead.next = newNode; // 头节点指向新节点
size++; // 链表长度加1
}
// 在链表尾部添加一个新节点,值为val
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = dummyHead; // 从虚拟头节点开始遍历
while (cur.next != null) {
cur = cur.next; // 移动到链表的最后一个节点
}
cur.next = newNode; // 将新节点添加到链表尾部
size++; // 链表长度加1
}
// 在链表的第index个位置之前添加一个新节点,值为val
public void addAtIndex(int index, int val) {
if (index > size) return; // 如果索引大于链表长度,不执行操作
ListNode newNode = new ListNode(val);
ListNode cur = dummyHead; // 从虚拟头节点开始遍历
for (int i = 0; i < index; i++) {
cur = cur.next; // 移动到指定索引的位置
}
newNode.next = cur.next; // 新节点指向当前节点的下一个节点
cur.next = newNode; // 当前节点指向新节点
size++; // 链表长度加1
}
// 删除链表的第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return; // 检查索引是否有效
ListNode cur = dummyHead; // 从虚拟头节点开始遍历
for (int i = 0; i < index; i++) {
cur = cur.next; // 移动到要删除节点的前一个节点
}
cur.next = cur.next.next; // 将当前节点的下一个节点的下一个节点指向当前节点的下一个节点,实现删除操作
size--; // 链表长度减1
}
public static void main(String[] args) {
MyLinkedList linkedList = new MyLinkedList(); // 初始化链表
linkedList.addAtHead(1); // 头部添加元素1
linkedList.addAtTail(3); // 尾部添加元素3
linkedList.addAtIndex(1, 2); // 在第1个位置之前添加元素2
System.out.println(linkedList.get(1)); // 获取第1个位置的元素值,输出:2
linkedList.deleteAtIndex(1); // 删除第1个位置的元素
System.out.println(linkedList.get(1)); // 再次获取第1个位置的元素值,输出:3
}
}
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:

输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000] -5000 <= Node.val <= 5000
解法1:迭代解法(双指针法)
迭代解法思路:使用三个指针:
prev、curr和next。prev用于指向当前节点的前一个节点,curr用于遍历链表,next用于暂存curr的下一个节点。
- 初始化
prev为null,curr为头节点head。 - 遍历链表,对于每个节点,先使用
next暂存curr.next。 - 然后,将
curr.next指向prev,实现反转。 - 更新
prev为curr,curr为next。 - 当
curr为null时,遍历结束,此时prev指向新的头节点。
代码如下:
// 定义链表节点类
class ListNode {
int val; // 节点存储的值
ListNode next; // 指向下一个节点的引用
// 构造函数,初始化节点的值和下一个节点
ListNode(int x) {
val = x;
next = null;
}
}
// 定义链表类
class Solution {
// 反转链表的方法
public ListNode reverseList(ListNode head) {
// 初始化两个指针,prev 和 curr,分别指向前一个节点和当前节点
// 初始化为null的prev指针和头节点curr
ListNode prev = null, curr = head;
// 使用while循环遍历链表
while (curr != null) {
// 暂存当前节点的下一个节点
ListNode next = curr.next;
// 将当前节点的next指针指向前一个节点,实现反转
curr.next = prev;
// 前一个节点指针前移一位
prev = curr;
// 当前节点指针移动到下一个节点(原来是下一个节点)
curr = next;
}
// 遍历结束后,prev指针指向新的头节点,返回prev
return prev;
}
// 主类,用于测试
public static void main(String[] args) {
// 创建链表 1 -> 2 -> 3 -> 4 -> 5
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
// 创建Solution对象
Solution solution = new Solution();
// 调用reverseList方法反转链表
ListNode newHead = solution.reverseList(head);
// 打印反转后的链表
printList(newHead); // 输出:5 -> 4 -> 3 -> 2 -> 1 -> null
}
// 辅助方法,用于打印链表
public static void printList(ListNode node) {
ListNode current = node;
while (current != null) {
System.out.print(current.val + " -> ");
current = current.next;
}
System.out.println("null");
}
}
示例:
1 -> 2 -> 3 -> 4 -> 5 -> null,我们的目标是将这个链表反转为:5 -> 4 -> 3 -> 2 -> 1 -> null
步骤0:初始状态
- 头节点
head指向数字 1。 prev指针初始化为null,因为它将指向反转后的链表的最后一个节点。curr指针初始化为head,即指向数字 1。next指针用于暂存curr.next。
步骤 1: 遍历链表并反转
-
第一次迭代:
curr指向 1,next指向 2。- 将
curr.next设置为prev(此时为null),所以 1 的下一个节点现在是null。 prev移动到curr(1),curr移动到next(2)。
原链表: 1 -> 2 -> 3 -> 4 -> 5 -> null 反转后: null <- 1 -> 2 -> 3 -> 4 -> 5 -> null -
第二次迭代:
curr指向 2,next指向 3。- 将
curr.next设置为prev(此时为 1),所以 2 的下一个节点现在是 1。 prev移动到curr(2),curr移动到next(3)。
原链表: 1 -> 2 -> 3 -> 4 -> 5 -> null 反转后: null <- 1 <- 2 -> 3 -> 4 -> 5 -> null -
第三次迭代:
curr指向 3,next指向 4。- 将
curr.next设置为prev(此时为 2),所以 3 的下一个节点现在是 2。 prev移动到curr(3),curr移动到next(4)。
原链表: 1 -> 2 -> 3 -> 3 -> 4 -> 5 -> null 反转后: null <- 1 <- 2 <- 3 -> 4 -> 5 -> null -
第四次迭代:
curr指向 4,next指向 5。- 将
curr.next设置为prev(此时为 3),所以 4 的下一个节点现在是 3。 prev移动到curr(4),curr移动到next(5)。
原链表: 1 -> 2 -> 3 -> 4 -> 5 -> null 反转后: null <- 1 <- 2 <- 3 <- 4 -> 5 -> null -
第五次迭代:
curr指向 5,next为null。- 将
curr.next设置为prev(此时为 4),所以 5 的下一个节点现在是 4。 prev移动到curr(5),curr移动到next(null)。
原链表: 1 -> 2 -> 3 -> 4 -> 5 -> null 反转后: null <- 1 <- 2 <- 3 <- 4 <- 5 -> null
步骤 2: 返回新的头节点
- 当
curr为null时,遍历结束。 prev现在指向新的头节点,即数字 5。
补充:链表理论基础
在Java中,链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的引用。Java提供了LinkedList类来实现链表,这个类继承自AbstractSequentialList类,实现了List接口、Queue接口、Deque接口、Cloneable接口和java.io.Serializable接口。
一、链表的基本概念
链表由节点组成,每个节点包含数据和指向下一个节点的引用。在Java中,可以通过创建一个节点类来表示链表的节点,如下所示:
class Node {
int data;
Node next;
Node(int data) {
this.data = data;
this.next = null;
}
}
二、链表的分类
- 单向链表:每个节点只包含一个指向下一个节点的指针。
- 双向链表:每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点。
- 循环链表:最后一个节点的下一个节点指向头节点,形成一个环。
三、链表的操作
链表的基本操作包括插入、删除、查找和遍历。以下是一些基本操作的示例代码:
- 插入操作
-
头插法:在链表头部插入新节点。
public void addFirst(int data) { Node newNode = new Node(data); newNode.next = head; head = newNode; } -
尾插法:在链表尾部插入新节点。
public void addLast(int data) { Node newNode = new Node(data); if (head == null) { head = newNode; } else { Node current = head; while (current.next != null) { current = current.next; } current.next = newNode; } }
- 删除操作
-
删除指定值的节点:遍历链表,找到并删除指定值的节点。
public void delete(int data) { if (head == null) return; if (head.data == data) { head = head.next; return; } Node current = head; while (current.next != null && current.next.data != data) { current = current.next; } if (current.next != null) { current.next = current.next.next; } }
- 查找操作
-
查找指定值的节点:遍历链表,返回指定值的节点。
public Node find(int data) { Node current = head; while (current != null && current.data != data) { current = current.next; } return current; }
- 遍历操作
-
打印链表:遍历链表并打印每个节点的值。
public void printList() { Node current = head; while (current != null) { System.out.print(current.data + " "); current = current.next; } System.out.println(); }
复杂度分析
- 插入和删除操作的时间复杂度为O(1),因为这些操作只需要修改指针。
- 查找操作的时间复杂度为O(n),因为最坏情况下需要遍历整个链表。
总结
链表是一种灵活的数据结构,特别适合于插入和删除操作频繁的场景。通过实现链表的基本操作,可以解决许多与链表相关的算法问题。在Java中,LinkedList类提供了丰富的方法来操作链表,使得链表的使用更加方便和高效。

1400

被折叠的 条评论
为什么被折叠?



