链表系列文章(二)

上一篇文章实现了链表的基本数据结构,这一篇谈一谈关于链表的一些有趣的题目

首先给出链表节点的数据结构

1 struct ListNode {
2     int var;
3     ListNode *next;
4     ListNode(int v = 0 ) : var(v), next(NULL) {  }
5 };

1. 删除链表的倒数第K个节点

例如:给定链表:1->2->3->4->5, n = 5, 返回2->3->4->5

方法一:常规做法,两次扫描,第一次扫描得到链表的长度,然后求得待删除节点的正向索引,第二次扫描删除节点

时间复杂度O(n), 空间复杂度O(1)

 1 ListNode *removeNthNodeFromEndOfList(ListNode *head, int k) {
 2     if( !head || k < 1 ) return NULL;
 3     int len = 0;
 4     ListNode *p = head, newhead(-1);
 5     newhead.next = p;
 6     for( ; p; p = p->next ) len++;//第一遍扫描,得到链表长度
 7     k = len - k;//得到正向索引的前一个节点的索引
 8     if(k < 0) return NULL;
 9     p = &newhead;
10     while( k-- ) p = p->next;//第二遍扫描,定位待删除节点的前一个节点
11     
12     ListNode *tmp = p->next;
13     p->next = tmp->next;
14     delete tmp;
15     return newhead.next;
16 }

方法二:两个指针, 第一个指针先走K步,然后两个指针同时走,当第二个指针走到链表尾时,第一个指针指向的节点就是要删除的节点

时间复杂度O(n), 空间复杂度O(1)

 1 ListNode *removeNthNodeFromEndOfList(ListNode *head, int k) {
 2     if(!head || k < 1)    return NULL;
 3     ListNode newhead(-1), *p = &newhead, *q = &newhead;
 4     newhead.next = head;      
 5     while( k-- && q ) q = q->next; //q->next指向正向第k个节点
 6     if( !q ) return NULL;
 7     while( p && q->next ) { p = p->next; q = q->next; }//p->next就是要删除的节点
 8     q = p->next;
 9     p->next = q->next;
10     delete q;
11     return newhead.next;
12 }

2.  复制带随机指针的链表,随机指针要么指向链表中的节点要么为空

1 struct RandomListNode {
2     int var;
3     RandomListNode *next, *random;
4     RandomListNode(int v) : var(v), next(NULL), random(NULL) { }
5 };

方法一:常规做法,先扫面一遍链表复制next指针;然后对于每个节点,定位随机节点在链表中的索引,然后复制每个random节点时,通过扫描next指针

得到定位索引对应的节点,将random指针指向该节点

时间复杂度O(n2), 空间复杂度O(n)

 1 RandomListNode *copyRandomList(RandomListNode *head) {
 2     if( !head ) return NULL;
 3     int n = 0;
 4     RandomListNode *p = head, newhead(-1), r = &newhead;
 5     for( ; p; p = p->next ) {//扫描链表,复制next指针,同时记录链表长度
 6         r->next = new RandomListNode(p->var);
 7         r = r->next; n++;
 8     }
 9     r = &newhead;
10     for(p = head; p; p = p->next) {//循环复制random指针
11         if(NULL == p->random)
12             r->next->random = NULL;
13         else if(p->random == p)
14             r->next->random = r->next;
15         else {//指向链表中的其它节点
16             int k = n;
17             RandomListNode *q = p->random;
18             RandomListNode *h = newhead.next;
19             while(q) {k--; q = q->next;}//random在源链表中的正向索引
20             while(k--) {h = h->next;}//在复制链表中找到该节点
21             r->next->random = h;
22         }
23         r = r->next;
24     }
25     return newhead.next;
26 }

方法二:将复制链表的节点一一插入到源链表的对应节点后:

如源链表(只显示next指针):s1->s2->s3->s4

插入后:s1->d1->s2->d2->s3->d3->s4->d4,比如要复制d1的random指针,只需d1->random = s1->random->next即可

时间复杂度O(n), 空间复杂度O(n)

 1 RandomList *copyRandomList(RandomListNode *head) {
 2     if( !head ) return NULL;
 3     RandomListNode newhead(-1);
 4     for( RandomListNode* p = head; p != NULL ) { //一一插入复制链表节点到对应源链表节点后
 5         RandomListNode* q = new RandomListNode(p->var);
 6         q->next = p->next;
 7         p->next = q;
 8         p = q->next;
 9     }
10     for( RandomListNode* p = head; p != NULL ) {//复制random指针
11         if (p->random != NULL)
12             p->next->random = p->random->next;
13         p = p->next->next;
14     }
15     for( RandomListNode* p = head, *d_p = &newhead; p != NULL ) {//恢复两个链表
16         d_p->next = p->next;
17         d_p = d_p->next;
18         p->next = p->next->next;
19         p = p->next;
20     }
21     return newhead.next;
22 }

3. 判断链表是否有环,如果有,找到交点

判断有环:

方法一:hash,用hash表记录每个节点的地址,当第一次遇到hash值存在时,表明相交,同时找到了第一个节点,否则如果走到空,则无交点

             时间复杂度O(n), 空间复杂度O(n)

方法二:快慢指针,fast指针每次走两步,slow指针每次走一步,因此fast指针相对slow指针每次走一个,如果有环,必然在有限步相交,至于判断交点,参考博文:

             http://www.cnblogs.com/carlsama/p/4127201.html

由于本人水平有限,文章中难免有不让和错误之处,欢迎大家批评指正,愿共同进步!!!

原文地址:https://www.cnblogs.com/zhuoyuan/p/4130049.html