给定一个链表,删除从链表尾数起第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; }