【LeetCode】Remove Nth Node From End of List

给定一个链表,删除从链表尾数起第n个节点,并返回头节点。

e.g. 

给定链表:1 -> 2 -> 3 -> 4 -> 5,n = 2

删除倒数第二个节点后的链表: 1 -> 2 -> 3 -> 5

我的笨方法:

原理是判断要删除的节点为从头数起第 count 个节点,然后判断是否为头节点,进行删除。

 1     int length(ListNode* head) {
 2         int n = 0;
 3         ListNode *p = head;
 4         while (p) {
 5             p = p -> next;
 6             n++;
 7         }
 8         return n;
 9     }
10     
11     ListNode* removeNthFromEnd(ListNode* head, int n) {
12         int count = length(head) - n + 1;
13         if (count == 1) {
14             ListNode *p = head;
15             head = head -> next;
16             delete p;
17         } else if (count-- > 1) {
18             ListNode *p = head, *q;
19             while (count) {
20                 q = p;
21                 p = p -> next;
22                 count--;
23             }
24             q -> next = p -> next;
25             delete p;
26         }
27         return head;
28     }

看答案看到一个使用二级指针的方法,惊为天人。

 1 ListNode* removeNthFromEnd(ListNode* head, int n) {
 2     ListNode** t1 = &head, *t2 = head;
 3     // t2向后移n个节点
 4     while (n--) t2 = t2->next;  
 5     // 使t2移到最后一个节点的next,即NULL
 6     // t1指向那个指向待删除节点的指针,即指向待删除节点的上一个节点的next
 7     while (t2 != NULL) {
 8         t2 = t2->next;
 9         t1 = &((*t1)->next);
10     }   
11     t2 = *t1;   // t2指向待删除节点
12     (*t1) = t2->next;
13     delete t2;
14     return head;
15 }

原理:

    该方法 4 - 11 行直接正序找到倒数第 n 个节点。

    如本例 n = 2 时,t2 先向后移动 2 个节点到 3 ,然后 t1 和 t2 一起向后移动,当 t2 移动到最后的 NULL 时,t1 指向那个【指向待删除节点 4 的指针】,即指向待删除节点 4 的上一个节点 3 的next。最后 t2 = *t1; 则让 t2 指向待删除节点 4 。

二级指针在链表中的使用

参考 http://coolshell.cn/articles/8990.html

    一般人以及多数教材在实现删除链表节点的函数的时候,维护了一个 prev 指针,然后删除当前指针 cur 。

while (cur)  
{  
    if (cur->data == tdata) {  /* 删除data == tdata的链表节点 */
        /* 如果待删除的节点是链表头 */  
        if (cur == head) {
            head = cur->next;  
        } else {   /* 如果待删除的节点不是链表头 */
            prev->next = cur->next;  
        }  
        delete cur;    
    }  
    prev = cur;  
    /* 遍历所有节点 */  
    cur = cur->next;  
}  

这种一般方法是通过 head 指向的结构体实例以及所有 next 指针指向的结构体实例来遍历链表的。

    而如果使用二级指针,可以把头结点和其他节点等效对待,为什么呢?因为链表中每个节点都由前面一个节点的 next 指针来指向,而头结点是由 head 指针指向的,所以 head 指针和其他节点的 next 指针扮演着完全相同的角色。因此二级指针 **cur 使用 head 和所有 next 指针来遍历链表

while (*cur) {  
    entry = *cur;  
    if (entry->data == tdata) {  
        /* 删除 entry 节点 */  
        *cur = entry->next;  
       delete entry;  
    }  
    /* 遍历所有节点的指针 */  
    cur = &(entry->next);  
} 

在C++中,变量名的意义是拷贝一个值。之前的常见做法,cur = head->next,仅仅是把指针 head->next 的值copy给了指针 cur,使得 cur 可以访问链表的下一个节点;而声明为二级指针的 cur,*cur 不是 head->next 的copy值,它“就是” head->next 这个变量名的别名,它获得的是 head->next 这个变量的读写权。

不过个人认为这两种方法没有孰优孰劣,都声明了 2 个指针。。。

最有用的还是用两个指针正序遍历链表找到倒数第 n 个节点,因此可以写出最简洁的一般算法。

C++实现:

 1 ListNode* removeNthFromEnd(ListNode* head, int n) {
 2     ListNode *t1 = head, *t2 = head;
 3     while (n--) t2 = t2->next;
 4     if (!t2)    return head->next;  // 要删除的是头节点  
 5     t2 = t2->next;    // t2再后移一个节点,保证t1、t2之间间隔n个节点,这样t2变为NULL时,t1为待删除节点的前驱
 6     while (t2) {
 7         t1 = t1->next;
 8         t2 = t2->next;
 9     }
10     t2 = t1->next;    // t2为待删除节点
11     t1->next = t2->next;
12     delete t2;
13     return head;
14 }

PS:

C++对应的Java代码

ListNode *t1 = head;          ListNode t1=head;
head->next                    head.next
{ t2 = t1->next; t1->next = t2->next; ===> Java可直接写成 t1.next = t1.next.next; 不会有内存泄漏 delete t2; }
原文地址:https://www.cnblogs.com/wayne793377164/p/7112539.html