给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
- 解题思路
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针),最后一个节点的指针指向null(空指针)。 链表的入口节点称为链表的头节点,即head。
链表类型:
-
单链表
-
双链表
每一个节点有两个指针域,一个指向下一个节点,一个指向前一个节点。 双链表既可以向前查询,也可以向后查询。
-
循环链表
链表首尾相连
链表的存储方式:
链表通过指针域的指针链接在内存中的各个节点。
链表的操作:
- 删除节点
删除D节点,将C节点的next指针指向E节点即可。
- 添加节点
在添加节点时,需要注意操作顺序,如图,在添加F节点时,如果先将C节点的next指针指向F节点,那么
就会造成 “断链”。因此添加节点F时,要先将F节点的next指针指向D节点,再将C节点的next指针指向F节点。
综上所述,链表的增添和删除操作的时间复杂度都是O(1),不会影响到其他节点。
在遇到链表相关的问题时,要想链表和数组的数据存储底层区别,数组存储方式在检索元素和修改元素时很方便,根据数组的下标检索或修改即可,但是数组存储方式有一个问题,就是在插入元素和删除元素时比较麻烦,插入元素需要将该位置之后的元素全部后移,空出位置以便可以插入元素;同时,数组的元素是不能删的,只能覆盖。删除元素时需要将该位置之后的元素全部前移,覆盖掉待删除的元素。上述的操作导致仅仅是增删元素便花费了很多时间,同时浪费了资源。
使用链表存储方式在增删元素时,修改指针的指向即可,这样就大大方便了增删元素。
- 在做链表类的题目时要注意空指针问题,当出现空指针,就会运行出错。
解决这道题目时使用链表中删除节点的常规操作即可,但是要注意头节点的问题,如果头节点的值等于带求值的话,就要单独删除头节点,这个操作需要单独写一段代码。
代码如下:
class Solution {
public ListNode removeElements(ListNode head, int val) {
while ( head != null && head.val == val){
head = head.next;
}
if (head == null){
return head;
}
ListNode node = new ListNode();
ListNode preNode = new ListNode();
preNode = head;
node = preNode.next;
while (node != null) {
if (node.val == val) {
preNode.next = node.next;
node = preNode.next;
}else {
preNode = preNode.next;
node = node.next;
}
}
return head;
}
上述处理头节点时需要单独的一段代码逻辑,是否有方法可以以一种统一的逻辑来移除头节点?
可以设置一个虚拟头节点,这个虚拟头节点为新的头节点,此时移除头节点也就是删除节点的常规操作了。
同时在最后返回时,要记得需要返回头节点的后继节点。
代码如下:
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
ListNode node = new ListNode(-1, head);
ListNode pre = node;
ListNode cur = head;
while (cur != null ){
if (cur.val == val){
pre.next = cur.next;
}else {
pre = cur;
}
cur = cur.next;
}
return node.next;
}
}
上面两种解法的时间复杂度都是O(n),空间复杂度都为O(1)。