一、移除链表元素
题目:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
示例 1:
输入: head = [4,5,1,9], val = 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1 输出: [4,5,9] 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
这里我采用同一处理链表的方法,虚拟节点法,这个方法的好处就是可以将所有节点一视同仁,(可能会涉及到头节点的删除),下面根据代码进行具体的解释
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
首先,定义链表 val表示所需删除的元素,next表示指向当前指针的下一位元素
public ListNode deleteNode(ListNode head, int val) {
if(head==null){
return head;
}
ListNode dummy = new ListNode(-1,head);
ListNode pre = dummy;
ListNode cur = head;
while(cur!=null){
if(cur.val==val){
pre.next=cur.next;
}else{
pre=cur;
}
cur = cur.next;
}
return dummy.next;
}
这里就是具体的操作了,下面一步一步具体分析
if(head==null){
return head;
}
如果 head 为 null,即链表为空,直接返回 null,因为没有节点可以删除。
ListNode dummy = new ListNode(-1, head);
创建一个 dummy 节点,值为 -1,并将 head 节点设置为其后继节点。这么做是为了统一处理删除操作,因为可能会涉及到头节点的删除。
ListNode pre = dummy;
ListNode cur = head;
pre 是一个指向当前节点的前一个节点的指针,初始化为 dummy 节点,表示从虚拟头节点开始。 cur 是当前节点的指针,初始化为 head,从链表的头节点开始遍历。
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
这里就是循环遍历的重点了
1、首先使用 while 循环遍历链表,直到 cur 指针为空(遍历完整个链表)。
2、在循环中,检查当前节点 cur 的值是否等于 val。 如果相等,说明当前节点需要删除,将 pre.next 指向 cur.next,跳过当前节点,实现删除操作。
3、如果不相等,将 pre 指针移到当前节点 cur,继续遍历下一个节点。
return dummy.next;
最后返回 dummy 节点的下一个节点作为新的链表头节点,因为 dummy 是一个额外节点,实际链表从 dummy.next 开始。
二、设计链表
这一节需要实现有关链表的增删查操作,属于链表的基本操作,熟悉了这一节后对链表就会有更深一步的理解,这里主要使用的是单链表
实现 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
的节点。
具体应该如何做呢,首先同样应该初始化链表
1、链表初始化
class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
}
ListNode 类表示链表的节点。每个节点包含一个整数值 val 和一个指向下一个节点的引用 next。
2、实现MyLinkedList类
class MyLinkedList {
//size存储链表元素的个数
int size;
//虚拟头结点
ListNode head;
//初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode currentNode = head;
//包含一个虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next;
}
return currentNode.val;
}
//在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0, val);
}
//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
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;
}
//删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pred = head;
for (int i = 0; i < index ; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
下面具体解释各种方法操作
查询操作
public int get(int index) {
if (index < 0 || index >= size) {
return -1; // 如果索引越界,返回 -1
}
ListNode currentNode = head;
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next; // 遍历找到第 index+1 个节点
}
return currentNode.val; // 返回第 index 个节点的值
}
get(int index) 方法: 获取链表中第 index 个节点的值。 如果 index 超出链表范围,则返回 -1。
添加操作
代码:
在头部添加
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; // 如果索引超出链表长度,则不插入
}
if (index < 0) {
index = 0; // 如果索引小于 0,则插入到头部
}
size++;
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next; // 找到第 index 个节点的前一个节点 pre
}
ListNode toAdd = new ListNode(val); // 创建新节点
toAdd.next = pre.next; // 将新节点插入到 pre.next 的位置
pre.next = toAdd;
}
addAtIndex(int index, int val) 方法: 在链表中第 index 个位置插入一个值为 val 的节点。 如果 index 大于链表长度,则不插入。 如果 index 小于 0,则插入到头部。 使用 pre 找到第 index 个节点的前一个节点,将新节点插入到其后。
这里应该注意,当插入的数据在链表中时,首先要找到索引数的前一个指针对象,然后将新的数据的指针指向这个索引数,再由这个索引数的指针对象指向新的数据,顺序不能颠倒
先连接这里
再连接这里
删除操作
代码:
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return; // 如果索引越界,则不删除
}
size--;
if (index == 0) {
head = head.next; // 删除头节点
return;
}
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next; // 找到第 index 个节点的前一个节点 pre
}
pre.next = pre.next.next; // 将 pre.next 指向 pre.next.next,实现删除操作
}
deleteAtIndex(int index) 方法: 删除链表中第 index 个节点。 如果 index 超出链表范围,则不删除。 如果 index 为 0,直接将 head 指向 head.next,删除头节点。 使用 pre 找到第 index 个节点的前一个节点,将其指向 pre.next.next,实现删除操作。
具体示例
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
更多详细讲解可参考
https://www.programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html https://leetcode.cn/problems/design-linked-list/description/
今天的学习就到这里了