19. 删除链表的倒数第N个节点
1. 题目描述
题目链接
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
2. 题目分析
删除链表的的倒数第N个节点,主要有三种方法:
- 暴力法,既然是倒数第N个节点,那么直接将链表反转,然后找到第N-1个节点,删除第N个节点就OK了,简单暴力,但一般使用暴力破解都是下下策,即在没法保证更优的解决方法时,不管白猫黑猫,能拽老鼠的就是好猫。
- 两次遍历法,首次遍历链表,计算节点长度listLenth,再次遍历,找到要删除的节点n的前一个节点,即listLenth-n的位置。
- 一次遍历法:双节点遍历,当第一个节点遍历到n时,第二节点从头结点跟第一个节点一起往右移动,当第一个节点到达尾节点时,第二节点正好停留在要删除节点的前一个节点。
3. 解决思路
主要讲解一下注意事项,即,出题人挖好的坑。
- 如果链表长度小于n,那么就不需要删除节点;
- 如果删除的节点是第一个节点(头结点),那么需要特殊处理,而不是直接p.next = p.next.next;
- 如果删除的节点时最后一个节点(尾节点),那么也需要特殊处理,而不是直接p.next = p.next.next;
第一点好解决,判断一下统计的链表长度是否小于n就行了,后面两点就比较麻烦,一开始,我总在考虑怎么在while循环的遍历的时候(使用方法三的时候,方法二不存在第二和第三个注意事项)怎么处理这种特殊情况,但一直失败了,查看别人的解决方案后,发现一个非常棒的思路,即在原来的链表前添加一个新的头结点,这样就完全可以忽略第二和第三个注意事项了(上一博客还分析了自定义链表是否需要头结点的优势,现在就忘了,呜呜呜)。
4. 代码实现(java)
方法一,有兴趣的同学自己去玩玩,我没有使用这种方法,所以不贴方法一的源码了。
方法二:耗时16ms
public static ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null || n<1){
return head;
}
ListNode p = head;
int listLenth = 0;
while (p != null){
p = p.next;
listLenth++;
}
//n的长度大于链表长度
if (listLenth < n){
return head;
}
//删除头结点
if (listLenth == n){
return head.next;
}
int count=1;
p = head;
while (count < (listLenth-n)){
p = p.next;
count++;
}
p.next = p.next.next;
return head;
}
方法三:耗时8ms
public static ListNode removeNthFromEnd1(ListNode head, int n) {
if (head == null || n<1){
return head;
}
ListNode newHead = new ListNode(-1);//随便给个值,不做使用
newHead.next = head;
ListNode first = newHead.next;
ListNode second = newHead;
int count = 0;
while (first != null){
first = first.next;
count++;
if (count > n){
second = second.next;
}
}
if(count < n){
return head;
}
second.next = second.next.next;
return newHead.next;
}