删除链表的倒数第N个节点

删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

示例 1:
在这里插入图片描述

输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5] 示例 2:

输入:head = [1], n = 1 输出:[] 示例 3:

输入:head = [1,2], n = 1 输出:[1

思路

这一题怎么说?上一篇文章刚介绍了双指针算法:反转一个单链表。这一题正好用上了。
其实链表的常见的解法 就是虚拟头或者双指针。
这里删除倒数第N个节点的话,我们是必须要知道当前删除节点的前置节点。所以这里思考几个问题。

是否需要虚拟节点

当然需要啦,之前一直强调的,链表的删除操作一定是需要虚拟节点做一些边界特殊处理。

如何找到需要删除的节点

  1. 遍历两遍,找到需要删除的节点,记下当前前置节点,伪代码:pre.next = pre.next.next。但是,请注意:本题是需要你只遍历一遍,所以这种方式不太适合。
  2. 双指针:我们看上图:需要删除倒数第二个节点:4,那必须的一个节点指向3,一个节点指向5,或者 5.next(大家思考下,这里如果是5或者5.next会对代码有什么影响)
删除倒数第几个节点节点1节点2节点1和节点2个距离(括号对应括号)
1node1->4node2->5(node2->5.next)1(2)
2node1->3node2->5(node2->5.next)2(3)
3node1->2node2->5(node2->4.next)3(4)
4node1->1node2->5(node2->3.next)4(5)

节点1:删除节点的前置节点(很重要)
节点2:while循环退出的条件:到底是最后一个节点退出、还是最后一个节点的下一个节点为null退出。两种while结束条件是不一样的。

动图展示

我们以 lastNode.next == null 作为结束条件举例。如果是 lastNode.next == null,那 node1和node2之间的距离是几呢
n+1 ,为什么是n+1,因为这样,slow移动才能指向到删除节点的上一个节点。看下图:
在这里插入图片描述
在这里插入图片描述
slow 和 fast同时移动,直到fast == null在这里插入图片描述删除slow指向的下一个节点,如图:在这里插入图片描述

代码

代码我一共给出三份,每份我都执行成功过,大家看的时候时候,一定要熟悉每个变量的意义,比如什么时候n+1,什么时候n。比如while(fast ==null) 还是while(fast.next == null ),这些都不是固定,哪种都可以,但是需要跟你自己定义的变量相对应。

/**
 * Definition for singly-linked list.
 * public 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 Solution {
    public ListNode removeNthFromEnd2(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head);
        ListNode fast = dummy;
        ListNode slow = dummy;
        while (n > 0) {
            fast = fast.next;
            n--;
        }
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }

    public ListNode removeNthFromEnd3(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head);
        ListNode fast = dummy;
        ListNode slow = dummy;
        //注意这里:我是从虚拟节点开始的,所以需要N+1
        while (n >= 0) {
            fast = fast.next;
            n--;
        }
        //注意这里:是fast。不是fast.next
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }

    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head);
        //注意,这里本身已经走了第一步,所以下面n>0
        ListNode fast = dummy.next;
        ListNode slow = dummy;
        while (n > 0) {
            fast = fast.next;
            n--;
        }
        //注意这里是fast而不是fast.next
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }

总结

  1. 双指针在解决这种只允许遍历一遍的问题上有很好的效果。
  2. 熟悉每个变量的意义

附:
以上图都来自于代码随想录

  • 32
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值