LeetCode-19-删除链表的倒数第N个节点
in LeetCode with 0 comment

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

in LeetCode with 0 comment

原题地址:删除链表的倒数第N个节点

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

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

进阶:

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

准备工作

定义链表结点:

function ListNode(val) {
    this.val = val;
    this.next = null;
}

数组转链表,方便测试数据的输入:

function arrayToList(array) {
    let node = null;
    for (let i = array.length - 1; i >= 0; i --) {
        let newNode = new ListNode(array[i]);
        newNode.next = node;
        node = newNode;
    }
    return node;
}

链表转字符串,方便测试结果的显示:

function listToString(node) {
    return node ? `${node.val}${node.next ? ` -> ${listToString(node.next)}` : '' }` : null;
}

两轮遍历

不考虑题目要求的一轮遍历,本题还是比较简单的。只需要通过一次遍历找到倒数第 n 个结点,再通过一轮遍历删除它就可以了:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
const removeNthFromEnd1 = function(head, n) {
    // 统计链表结点数量
    let count = 0;
    let node = head;
    while (node) {
        count ++;
        node = node.next;
    }
    // 要删除的是头结点
    if (count === n) {
        return head && head.next;
    }
    // 删除倒数第n个结点
    node = head;
    for (let i = 0; i < count - n - 1; i ++) {
        node = node.next;
    }
    node.next = node.next.next;
    return head;
};

测试:

let start = new Date();
const test = removeNthFromEnd1;
console.log(listToString(test(arrayToList([1, 2, 3, 4, 5]), 2))); // 1 -> 2 -> 3 -> 5
console.log(listToString(test(arrayToList([1]), 1))); // null
console.log(listToString(test(arrayToList([1, 2]), 2))); // 2
console.log(new Date().getTime() - start.getTime()); // 4

时间复杂度: 遍历两次,时间复杂度为O(2n) = O(n)
空间复杂度: 只需要固定大小的额外空间,空间复杂度为O(1)

双指针法

两次遍历比较简单,但是不符合题目一次遍历的要求。题目要求的是删除倒数第 n 个结点,我们可以通过双指针来优化。设置快慢两个指针,快指针先走 n 步,然后快慢指针同步走,这样两指针之间的距离就保持在了 n。只要快指针到了链表终点,通过慢指针就可以删除倒数第 n 个结点:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
const removeNthFromEnd2 = function(head, n) {
    let [slow, fast] = [head, head];
    // 快指针先走n步
    for (let i = 0; i < n; i ++) {
        fast = fast.next;
    }
    // 快指针已经走完了链表,证明要删除的是头结点
    if (!fast) {
        return head && head.next;
    }
    // 快慢指针同步移动
    while (fast.next) {
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;
    return head;
};

测试:

let start = new Date();
const test = removeNthFromEnd2;
console.log(listToString(test(arrayToList([1, 2, 3, 4, 5]), 2))); // 1 -> 2 -> 3 -> 5
console.log(listToString(test(arrayToList([1]), 1))); // null
console.log(listToString(test(arrayToList([1, 2]), 2))); // 2
console.log(new Date().getTime() - start.getTime()); // 3

时间复杂度: 一次遍历,时间复杂度为O(n)
空间复杂度: 空间复杂度为O(1)