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

231 阅读2分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

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

示例 1:

image.png

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

示例 2:

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

示例 3:

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

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

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

确定学习目标

对《删除链表的倒数第 N 个结点》的算法过程进行剖析。

剖析

我们先确认下两个要主要功能:

  1. 快速定位要删除的第N个结点。
  2. 删除节点

删除节点没啥好说的,链表节点移除而已。

那么怎么快速定位要删除的节点呢?我们很清楚:链表删除和插入的效率比较高,但是随机访问效率比较低,那我们是不是可以直接遍历一遍塞进list呢?当然可以,其实我也想过遍历一遍效率会比较低但是,数据结构已经给了,你要重新构造其他数据结构必须遍历一遍。所以我们可以使用list就行了。

还有一个问题,给定方法的参数直接是带关键字的头节点,如果要删除中间或者最后的节点那还好,如果删除头节点的话就没头节点了,所以我们直接再设置一个头节点nodeHead,这样我们就能正常对链表进行删除,结果直接返回nodeHead.next就行了。

还有一个需要注意的就是,没有pre指针,我们需要使用list去访问要删除的头节点的前面一个节点,需要注意数组越界。

下面直接上代码。

代码

import java.util.ArrayList;
import java.util.List;

/**
 * 19. 删除链表的倒数第 N 个结点
 * https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
 * 
 * 我们先确认下两个要主要功能:
 * 1. 快速定位要删除的第N个结点。
 * 2. 删除节点
 *
 * 删除节点没啥好说的,链表节点移除而已。
 *
 * 那么怎么快速定位要删除的节点呢?我们很清楚:链表删除和插入的效率比较高,但是随机访问效率比较低,那我们是不是可以直接遍历一遍塞进list呢?当然可以,其实我也想过遍历一遍效率会比较低但是,数据结构已经给了,你要重新构造其他数据结构必须遍历一遍。所以我们可以使用list就行了。
 *
 * 还有一个问题,给定方法的参数直接是带关键字的头节点,如果要删除中间或者最后的节点那还好,如果删除头节点的话就没头节点了,所以我们直接再设置一个头节点nodeHead,这样我们就能正常最链表进行删除,结果直接返回nodeHead.next就行了。
 */
public class RemoveNthFromEnd {
    /**
     * 头节点
     */
    private ListNode nodeHead = new ListNode();
    /**
     * 存放节点的list,用于快速定位要删除的节点
     */
    private List<ListNode> nodeList = new ArrayList<>();
    /**
     * 节点个数
     */
    private int nodeCount = 0;

    /**
     * 节点结构
     */
    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;
        }
    }

    /**
     * 移除倒数第n个节点
     *
     * @param head
     * @param n
     * @return
     */
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //使我们新增加的头节点字段和方法传参头节点连接上
        nodeHead.next = head;

        /**
         * 准备遍历节点装进list里面,add的同时nodeCount++
         */
        ListNode forNode = head;

        while (forNode.next != null) {
            nodeList.add(forNode);
            nodeCount++;
            forNode = forNode.next;
        }

        nodeList.add(forNode);
        nodeCount++;

        //定位出要删除的节点
        ListNode delNode = nodeList.get(nodeCount - n);

        //获得要删除节点前面一个节点的index方便后面删除节点
        int delNodeBeforeIndex = nodeCount - n - 1;

        //需要注意删除的节点前面一个节点的index是否数组越界了,如果越界了直接用头节点
        ListNode delNodeBeforeNode;
        if (delNodeBeforeIndex < 0) {
            delNodeBeforeNode = nodeHead;
        } else {
            delNodeBeforeNode = nodeList.get(delNodeBeforeIndex);
        }

        //删除节点的下一个节点方便后面删除节点
        ListNode delNodeNextNode = delNode.next;
        delNodeBeforeNode.next = delNodeNextNode;

        return nodeHead.next;
    }
}