链表10:链表中删除元素的8道题之一
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表10:链表中删除元素的8道题之一相关的知识,希望对你有一定的参考价值。
如果一道道刷题,你会发现算法题目毫无章法,但是如果将相关类型放在一起,你瞬间发现,这不就是在改改条件、不断造题吗?我们前面已经多次见证这个情况,今天再来看一个链表中删除元素的造题合集。如果在链表中删除元素搞清楚了,一下子就搞定了8道LeetCode题,是不是很爽?
1.题目
如果只看下面这些文字要求,眼睛会有点花、脑子会有点晕。没关系,我们将其分成了三组,用三期来会逐个拆解。这里你只要注意到这些题目要求差不多就行了。
第一组:
【1】LeetCode 237:删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为要被删除的节点 。
【2】LeetCode 203:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。
第二组:
【3】LeetCode 19. 删除链表的倒数第 N 个节点
【4】LeetCode 1474. 删除链表 M 个节点之后的 N 个节点。
第三组:
【5】LeetCode 83 存在一个按升序排列的链表,请你删除所有重复的元素,使每个元素只出现一次。
【6】LeetCode 82 存在一个按升序排列的链表,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中没有重复出现的数字。
【7】LeetCode 1836. 从未排序链表中删除重复元素。
下面这个题目比较特殊,我们在后面分析nSum问题的时候统一来看:
【8】LeetCode 1171 请你编写代码,反复删去链表中由总和值为 0 的连续节点组成的序列,直到不存在这样的序列为止。
这一期我们先拆解第一组的问题。
在链表中删除元素有两种要求,一种是给定要删除的位置编号来删,一种是根据传入的值value,先找到值为value的位置,然后将其删除。对于后一种,题目可能要求找到一个删除就行了,也可能是将所有节点值为value的都删掉,LeetCode 237和203题貌似与后面两种情况很类似,但是仔细看发现还不太一样。我们一个个来分析。
1.单链表中如何删除节点
绝大部分情况 删除链表节点的操作套路都是固定的,这个我们在单链表的基本操作部分介绍过,这里再复习一下。
链表的删除主要是删除头部,删除尾部和中间位置三种情况。
我们依次看一下。
(1)删除表头元素
如下图,最容易晕的是遍历游标cur从4到15之后,head一定要指向cur,也就是要有head=cur的操作。废话不多说,看着这个图想明白就行了。
(2)删除表尾元素
核心思想是找到尾节点时,需要将其前驱的next设置为null。如果只用一个遍历游标,可以判断遍历游标的cur.next.next是否为空,因为cur.next.next为null时,就说明cur.next是尾节点了,此时只要将cur.next设置为null就行了,如下图:
cur为7的时,cur.next是40,是尾节点,因为cur.next.next=null。此时只要设置7的next指针为null,节点40就脱离链表了。之后节点40会在某个时刻被jvm回收。
(3)删除中间元素
删除的元素在中间时,如下图,如果删除7。因为链表是单向的,此时必须提前知道节点15的地址,否则就无法将15连接到40上。
所以完整的删除代码是:
/**
* 删除节点
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
if (head == null) {
return null;
}
int size = getListLength(head);
if (position > size || position <= 0) {
System.out.println("输入的参数有误");
return head;
}
if (position == 1) {
//curNode就是链表的新head
return head.next;
} else {
Node preNode = head;
int count = 1;
while (count < position) {
preNode = preNode.next;
count++;
}
Node curNode = preNode.next;
preNode.next = curNode.next;
}
return head;
}
如果自己手写这个方法,经常会出现没考虑首元素,没考虑尾元素等等情况,所以还是需要练习一下的。
2.一种罕见的元素删除方法
我们前面说LeetCode 237看似普通,但是又和平时做的题目不一样,具体怎么回事呢?我们看一下要求:
LeetCode 237:删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为要被删除的节点 。
示例1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
这里的意思是给你的node节点不是链表的头节点,而是直接要删除的节点。例如上面给的node=5,这个node是直接要删除的,而不是首节点,那就不用使用前面的方式先遍历找前驱,再删除了。
那该怎么删呢, 其实也不难,我们可以采用数组移动的思想,将node后面的元素值逐个覆盖其前面的元素就行了。
例如下面这个图:
我们要删除的node=3,那我们就用node.next的值4来覆盖3。然后后面的5覆盖4就可以了。这就是数组删除元素的套路嘛(又是套路)
所以,代码就可以这么写:
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
你是不是觉得写错了,为啥只有两行?但是确实想清楚之后,两行就解决了。
3.LeetCode 203 移除链表中的目标元素
LeetCode203就是一个常规的删除节点的题了。
我们先看一下完整的题目要求:
LeetCode203:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
一般情况下,我们删除节点cur时,必须知道其前驱pre节点和后继next节点,然后让pre.next=next就讲cur脱离开链表了。cur节点会在gc进行垃圾回收时回收掉。
对于删除来说,最麻烦的处理是如果删除的元素是首元素该怎么处理,因为其指针移动规律和后面的不一样,为了解决这个问题,我们可以先创建一个虚拟节点 dummyHead,使其指向head,也就是dummyHead.next=head,这样就不用单独处理首节点了。当然,在返回的时候注意要返回的地址是dummyHead.next,而不是dummyHead。
完整的步骤是:
1.我们创建一个虚拟链表头dummyHead,然后使用temp = dummyHead进行链表操作
2.开始循环head链表
3.当该节点的值不等于val时,将temp的next指向该节点,然后将temp指向它的next节点。
这里注意temp在结束时,需要将它的未节点指向None,避免出现符合条件的最后一次赋值后,链接指向错误问题。
代码实现过程:
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode temp = dummyHead;
while (temp.next != null) {
if (temp.next.val == val) {
temp.next = temp.next.next;
} else {
temp = temp.next;
}
}
return dummyHead.next;
}
}
以上是关于链表10:链表中删除元素的8道题之一的主要内容,如果未能解决你的问题,请参考以下文章