203.移除链表元素
代码随想录讲解
自己的思路是直接在原来的链表上删除:
但是以下代码会报错(这段代码没考虑首节点为删除节点):Cannot read field “next” because “” is null
我的疑惑是,为什么先判断temp.next不为空,再将temp=temp.next,为什么会出现temp为空,从而引发空指针异常。
其实也很简单,
当temp.next是要被移除的节点时,代码执行了temp.next =temp.next.next。因为删除的是当前节点指向的节点,这会导致多越过一个节点,使当前节点成为被删除节点J的指向的节点K,从而判断K指向的节点,导致K节点被越过。
如果temp.next.next是null(即temp.next是链表的最后一个节点,并且这个节点被移除),那么temp.next会被设置为null。在下一个循环迭代中,当执行temp = temp.next时,temp也会变成null。然后在循环的开始尝试访问temp.next时,就会引发空指针异常。(根据写206题,不管逻辑上temp是否为空,直接判断temp.next就会报错,必须先判定temp是否为空)
public static ListNode removeElements(ListNode head, int val) {
ListNode temp=head;
while(temp.next!=null){
if(temp.next.val==val){
temp.next=temp.next.next;
}
temp=temp.next;
}
return head;
}
为了解决这个问题,可以在赋值temp = temp.next后加入一个检查,以确保temp不是null。下面是更新后的代码:
public ListNode removeElements(ListNode head, int val) {
//特殊情况处理:当整个链表为空
if (head == null) {
return null;
}
//移除头部需要被移除的节点
while (head != null && head.val == val) {
head = head.next;
}
ListNode temp=head;
while(temp!=null&&temp.next!=null){
if(temp.next.val==val){
temp.next=temp.next.next;
//不需要在这里移动temp,因为temp.next已经更新
}else{
//只有当不删除节点时才移动temp
temp=temp.next;
}
}
return head;
}
链表操作的两种方式:
直接使用原来的链表来进行删除操作。
设置一个虚拟头结点在进行删除操作。
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
以一种统一的逻辑来移除 链表的节点:
可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
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;
}
707.设计链表
这道题目设计链表的五个接口:
获取链表第index个节点的数值
在链表的最前面插入一个节点
在链表的最后面插入一个节点
在链表第index个节点前面插入一个节点
删除链表的第index个节点
下面设置一个虚拟头结点在进行操作。
//单链表
class ListNode {
int val;
ListNode next;
//无参构造
ListNode(){}
//有参构造
ListNode(int val) {
this.val=val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
class MyLinkedList {
ListNode head;
//初始化链表
public MyLinkedList() {
//虚拟头节点
head=new ListNode();
}
//根据索引位置获取元素
public int get(int index) {
ListNode p=head;
int i=-1;
while(p!=null){
if(i==index){
return p.val;
}
p=p.next;
i++;
}
return -1;
}
//在头部插入
public void addAtHead(int val) {
ListNode temp=new ListNode(val);
temp.next=head.next;
head.next=temp;
}
//在尾部插入
public void addAtTail(int val) {
ListNode temp=new ListNode(val);
ListNode p=head;
while(p.next!=null){
p=p.next;
}
p.next=temp;
}
//根据索引位置插入
public void addAtIndex(int index, int val) {
ListNode temp=new ListNode(val);
ListNode p=head;
int i=0;
while(p.next!=null){
if(i==index){
temp.next=p.next;
p.next=temp;
break;
}
p=p.next;
i++;
}
//如果index等于链表长度,则直接插入尾部
if(i==index){
p.next=temp;
}
}
//根据索引位置删除
public void deleteAtIndex(int index) {
ListNode p=head;
int i=0;
while(p.next!=null){
if(i==index){
p.next=p.next.next;
break;
}
p=p.next;
i++;
}
}
}
代码随想录讲解
在类中直接定义一个常量来记录链表长度,更加简洁。
206.反转链表
双指针法,用head保存了每次fast节点改变指向前的节点。
做了个动态图:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while(head!=null&&head.next!=null){
fast=head.next;
head.next=fast.next;
fast.next=slow;
slow=fast;
}
return fast;
}
}