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

题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

阅读题目后,有两种思路。

两次遍历,我们首先用一个指针遍历链表,统计出链表的长度L,那么倒数第n个节点,就是正数的第( L - n + 1)个。然后我们另启用一个指针从头开始遍历到正数第(L - n)个节点,执行删除节点操作。(注:此方法需要考虑特殊情况,比如只有一个指针和要删除的指针是head指针所指的节点的时候)。

var removeNthFromEnd = function(head, n) {    
    // 如果链表长度为1
    if(head.next === null) {
        return null;
    }
    let count = 0; // 用于统计链表的长度
    let p = head;
    
    // 统计链表长度
    while(p !== null) {
        count++;
        p = p.next;
    }
    // 如果删除的节点是head
    if(count === n) {
        head = head.next;
    } else {
       //下面可考虑普通情况, 我们新建一个指针q指向head开始遍历,遍历到链表正数的第(count - n)个
        let q = head;
        for(let i = 1; i < count - n ; i++) {
            q = q.next;
        }

        // 执行删除操作
        q.next = q.next.next; 
    }
    return head;
};

上面的算法是需要考虑边界特殊情况的问题,那有没有什么方法优化呢?在主要思想相同的前提下,我们可以引入一个「哨兵节点」来消除上述特殊情况的考虑,可以用统一的操作来执行。

我们回想一下上一种方法中,我们要删除一个节点,则需要这个节点有「前继节点」,那么如果要删除的是head节点,则没有「前继节点」,head节点之后的节点都有「前继节点」,所以如果head节点也有「前继节点」,我们是不是就能统一操作了呢?故而我们创建一个「哨兵节点」指向head。注意:创建「哨兵节点」,相当于第0个节点,第二次遍历我们从「哨兵节点」开始,然后遍历到第(L - n)个,然后删除目标节点,返回的时候应该是「哨兵节点」的「后继节点」。

var removeNthFromEnd = function(head, n) {    
    let count = 0; // 用于统计链表长度
    let p = head;   // 用于第一次遍历
    while(p !== null) {
        count++;
        p = p.next;
    }
    
    // 创建一个「哨兵节点」指向head
    let k = new ListNode(0);
    k.next = head;
    let q = k;
    
    // 删除的是第( count - n + 1 )个,我们还是要找到第( count - n )个
    // 注意本次q的起点是那个「哨兵节点」,也就是第0个
    for(let i = 0; i < count - n; i++) {
        q = q.next;
    }
    
    // 执行删除操作
    q.next = q.next.next;
    
    return k.next;
};

你可能感兴趣的:(19. 删除链表的倒数第N个节点)