如果我们要删除节点 y,我们需要知道节点 y 的前驱节点 x,并将 x 的指针指向 y 的后继节点
但由于头节点不存在前驱节点,因此我们需要在删除头节点时进行特殊判断。但如果我们添加了哑节点,那么头节点的前驱节点就是哑节点本身,此时我们就只需要考虑通用的情况即可。
两种方法:
/**
* 获得本链表的长度
* @return
* @date 2016-9-28
* @author shaobn
*/
public int getLength(){
int length = 0;
Node tmpNode = this.headNode;
while(tmpNode!=null){
length++;
tmpNode = tmpNode.next;
}
return length;
}
}
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//初始化空节点
//将head链表加入,这样就算是头节点也可以遍历到
ListNode dummy = new ListNode(0, head);
int length = getLength(head);
ListNode cur = dummy;
//head的头节点在index 1 的位置
//length - n + 1表示倒数第n位在正数第几位
for(int i = 1; i < length - n + 1; ++i){
cur = cur.next;
}
cur.next = cur.next.next;
//dummy.next就是把头去掉了,最开始的0,还原成原本的链表
ListNode ans = dummy.next;
return ans;
}
//获取链表长度
//public int length!!!不是public void length!!!注意
public int getLength(ListNode head){
int length = 0;
while(head != null){
++length;
head = head.next;
}
return length;
}
}
时间复杂度:O(L),其中 L 是链表的长度。
空间复杂度:O(1)。
思路与算法
我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。
由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当first 遍历到链表的末尾时,second 就恰好处于倒数第 n个节点。
具体地,初始时first 和 second 均指向头节点。我们首先使用first 对链表进行遍历,遍历的次数为 n。此时,first 和 second 之间间隔了 n−1 个节点,即 first 比 second 超前了 n 个节点。
在这之后,我们同时使用first 和 second 对链表进行遍历。当first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n个节点。
根据方法一,如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将second 指向哑节点,其余的操作步骤不变。这样一来,当first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
//让first先走n位
for(int i = 0; i < n; ++i){
first = first.next;
}
//当first先走n位之后,first和second一起走
//first != null,也就是first走到最后的时候,second正好走到倒数第n位,引起dummy前加了一个0,所以second正好走到了倒数第n位的前置
while( first != null){
first = first.next;
second = second.next;
}
//把second的next删掉
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
时间复杂度:O(L),其中 L 是链表的长度。
空间复杂度:O(1)。