本文综合讲述几道删除链表元素的题目
一、LeetCode203.移除链表元素
给你一个链表的头节点 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 输出:[]
代码如下
lass Solution {
public ListNode removeElements(ListNode head, int val) {
// 创建一个虚拟头节点,并将其指向原始链表的头节点
ListNode dummyHead = new ListNode(0);
ListNode curr = head; // 当前节点指针,用于遍历链表
ListNode prev = dummyHead; // 前一个节点指针,用于构建新链表
dummyHead.next = head; // 虚拟头节点指向原始链表的头节点
// 遍历链表
while (curr != null) {
// 如果当前节点的值等于目标值
if (curr.val == val) {
// 将前一个节点的 next 指向当前节点的下一个节点,即删除当前节点
prev.next = curr.next;
// 移动当前节点指针到下一个节点
curr = prev.next;
} else {
// 如果当前节点的值不等于目标值
// 更新前一个节点指针为当前节点
prev = curr;
// 移动当前节点指针到下一个节点
curr = curr.next;
}
}
return dummyHead.next;
}
}
值得注意的点:
1.可能会有疑问,最后直接return head难道不是更加简介吗,何必用dummyhead.next多此一举
这是因为在原始链表的头部可能存在一个值等于 val
的节点,而删除这个节点后,新链表的头部会发生变化。
如果直接返回 head
,则可能返回的是一个值等于 val
的节点,而不是删除后的新链表的头节点。
2.ListNode dummyHead = new ListNode(0);
虚拟头结点的用法非常多,也很重要,需要多留意一下
二、LeetCode19.删除链表倒数第N个节点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
方法一:双指针
代码如下
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head, slow = head;
for (int i = 0; i < n; i++){
fast = fast.next;
}
if (fast == null) {
return head.next; // 删除的是头节点,直接返回头节点的下一个节点
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
}
前一期有关双指针的总结,涉及到倒数第N个元素的时候就可以考虑用它
在提交时,我没有写
if (fast == null) {
return head.next; // 删除的是头节点,直接返回头节点的下一个节点
}
导致报错
这段代码的主要原因是如果要删除的节点恰好是链表的头节点,也就是说 n
等于链表的长度时,代码会出现问题。
在这种情况下,fast
指针移动 n 步后将指向 null,而在代码的最后一行中,会尝试访问 slow.next
,而此时 slow
是指向头节点的,它的下一个节点为 null,因此会出现空指针异常。
为了修复这个问题,我们可以添加一个判断条件来检查 fast
是否为 null,如果是,则说明要删除的节点是头节点,直接返回头节点的下一个节点即可。
方法二:计算链表长度
思路:先遍历一遍链表,找到链表总长度L,然后重新遍历,位置L-N+1的元素就是我们要删的。
这里我就不写代码了,显然是用双指针更吊一点嘛,只要遍历一遍,而且更有逼格。
三、LeetCode83.删除排序链表中的重复元素 I
重复元素保留一个
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2] 输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3] 输出:[1,2,3]
代码如下
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) { // 如果头节点为空,直接返回空
return head;
}
ListNode cur = head; // 当前节点指针,初始化为头节点
while (cur.next != null) { // 遍历链表,直到当前节点的下一个节点为空(链表结尾)
if (cur.val == cur.next.val) { // 如果当前节点的值等于下一个节点的值,说明出现了重复的节点
cur.next = cur.next.next; // 将当前节点的 next 指针指向下一个节点的下一个节点,跳过重复节点
} else {
cur = cur.next; // 如果当前节点的值不等于下一个节点的值,将当前节点指针向后移动一位
}
}
return head; // 返回原始链表的头节点(可能经过删除操作后已经改变)
}
}
思路很简单:cur 和 curr 相比较,相同就删除,不同就 cur = cur.next
四、LeetCode83.删除排序链表中的重复元素 II
重复元素都不要
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
输入:head = [1,2,3,3,4,4,5] 输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3] 输出:[2,3]
代码如下
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) { // 如果头节点为空,直接返回空
return head;
}
ListNode dummy = new ListNode(0, head); // 创建一个虚拟节点,并将其 next 指针指向头节点
ListNode cur = dummy; // 当前节点指针,初始化为虚拟节点
while (cur.next != null && cur.next.next != null) { // 遍历链表,直到当前节点的下一个节点或下下个节点为空(链表结尾)
if (cur.next.val == cur.next.next.val) { // 如果当前节点的 next 节点的值等于下下个节点的值,说明出现了重复的节点
int x = cur.next.val; // 记录重复节点的值
while (cur.next != null && cur.next.val == x) { // 循环遍历直到找到不等于重复值的节点
cur.next = cur.next.next; // 将当前节点的 next 指针指向下一个节点的下一个节点,跳过重复节点
}
} else {
cur = cur.next; // 如果当前节点的 next 节点的值不等于下下个节点的值,将当前节点指针向后移动一位
}
}
return dummy.next; // 返回虚拟节点的下一个节点作为新的头节点(可能经过删除操作后已经改变)
}
}
这段代码实现了删除链表中重复出现的节点。通过比较当前节点的 next 节点和下下个节点的值。
如果值相等,则将当前节点的 next 指针指向下一个节点的下一个节点,跳过重复节点;
如果值不相等,则将当前节点指针向后移动一位。最后,返回虚拟节点的下一个节点作为新的头节点。
这道题对比前面几道就稍微有点难度了,要注意的细节很多,我是基本没思路的,看来答案的代码理解意思后,自己敲了一遍,结果还有几处不会,又看,又敲。确实值得多花时间的一道题。