面试算法之链表(C++)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试算法之链表(C++)相关的知识,希望对你有一定的参考价值。
参考文章:http://blog.csdn.net/anonymalias/article/details/11020477
链表定义
1 template <typename Type>
2 struct ListNode{
3 Type data;
4 ListNode *next;
5 };
尾插法创建链表(无头结点)
/** * Create a list, without head node */ template <typename Type> ListNode<Type> *CreatList(Type *data, int len) { if(data == NULL || len <= 0) return NULL; ListNode<Type> *head, *last; head = new ListNode<Type>; last = head; for (int i = 0; i < len; ++i) { last->next = new ListNode<Type>; last->next->data = data[i]; last = last->next; } last->next = NULL; last = head; head = head->next; delete last; return head; }
1. 单链表逆序打印
方法1:逆置+打印(费时费力)
方法2:使用显式栈结构(循环)
方法3:使用隐式栈结构(递归)
方法2(循环):
1 /** 2 * reversely print the list 3 * method 1: use the stack 4 */ 5 template <typename Type> 6 void ReversePrintList_1(const ListNode<Type> *head) 7 { 8 if(head == NULL) 9 return; 10 11 stack<Type> nodeStack; 12 while (head) 13 { 14 nodeStack.push(head->data); 15 head = head->next; 16 } 17 18 while(!nodeStack.empty()) 19 { 20 cout<<nodeStack.top()<<" "; 21 nodeStack.pop(); 22 } 23 cout<<endl; 24 }
方法3(递归):
1 /** 2 * reversely print the list 3 * method 2: recursively 4 */ 5 template <typename Type> 6 void ReversePrintList_2(const ListNode<Type> *head) 7 { 8 if(head == NULL) 9 return; 10 11 ReversePrintList_2(head->next); 12 cout<<head->data<<" "; 13 }
2. 单链表逆置
方法1:顺序扫描,用head指针从头到尾遍历原始链表,此时需要一个新链表,头结点为pre,逆置过程为
初始化:
pre=NULL
循环:
tmp = head->next;
head->next = pre;
pre = head;
head = tmp;
代码:
1 /** 2 * reverse the list 3 * method 1:sequential scanning 4 */ 5 template <typename Type> 6 ListNode<Type> * ReverseList_1(ListNode<Type> *head) 7 { 8 if(head == NULL) 9 return NULL; 10 11 ListNode<Type> *pre = NULL; 12 13 while (head) 14 { 15 ListNode<Type> *nextNode= head->next; 16 head->next = pre; 17 pre = head; 18 head = nextNode; 19 } 20 21 return pre; 22 }
方法2:递归,把链表划分为两部分,一是当前结点,一是已经逆置的子链表。我们需要做的是得到已经逆置的链表的最后一个节点,并把当前节点添到该节点的后面。
head:子链表的首节点
post:已经逆置子链表的首节点
newHead:逆置链表的首节点(原始链表的尾节点)
1 /** 2 * reverse the list 3 * method 2: recursion 4 */ 5 template <typename Type> 6 ListNode<Type> * ReverseList_2(ListNode<Type> *head) 7 { 8 if(head == NULL) 9 return NULL; 10 11 ListNode<Type> *newHead; 12 SubReverseList_2(head, newHead); 13 14 return newHead; 15 } 16 17 template <typename Type> 18 ListNode<Type> * SubReverseList_2(ListNode<Type> *head, ListNode<Type> *&newHead) 19 { 20 if (head->next == NULL) 21 { 22 newHead = head; 23 return head; 24 } 25 26 ListNode<Type> *post = SubReverseList_2(head->next, newHead); 27 post->next = head; 28 head->next = NULL; 29 30 return head; 31 }
3. 在O(1)时间删除链表中的node结点
方法:用下一个结点覆盖需要删除的结点
注意事项:
(1)首节点或者node结点为空;
(2)链表中只含一个结点(node结点和首节点为同一结点)
(3)node结点为尾节点
(4)其他结点,直接覆盖删除即可
1 /** 2 * delete a node from list 3 */ 4 template <typename Type> 5 ListNode<Type> * DeleteNode(ListNode<Type> *head, ListNode<Type> *node) 6 { 7 if(head == NULL || node == NULL) 8 return head; 9 10 //only have one node 11 if (node == head && node->next == NULL) 12 { 13 delete head; 14 return NULL; 15 } 16 17 //node counts > 1, and delete the tail node 18 if (node->next == NULL) 19 { 20 ListNode<Type> *pre = head; 21 22 while (pre->next != node) 23 pre = pre->next; 24 25 delete node; 26 pre->next = NULL; 27 28 return head; 29 } 30 31 //other node 32 ListNode<Type> *delNode = node->next; 33 node->data = delNode->data; 34 node->next = delNode->next; 35 36 delete delNode; 37 38 return head; 39 }
4. 链表中倒数第k个结点
方法1:(扫描2次)顺序扫描链表,统计链表长度n,再顺序扫描一次,第n-k个结点即为倒数第k个结点;
方法2:(扫描1次+O(n)辅助空间)顺序扫描链表,将结点保存在stack中,stack中的值依次弹出,第k个弹出的即为原始链表中倒数第k个结点;
方法3:类似方法2,使用递归实现;
方法4:(扫描1次+O(1)辅助空间)两个指针ahead,after;初始都指向首节点;after向后移动k-1个结点;ahead和after同时向后移动,当after指向尾节点是,ahead即为倒数第k个结点;
1 /** 2 * return the last k node from list, 1 =< k <= list length 3 */ 4 template <typename Type> 5 const ListNode<Type> * LastKNode(const ListNode<Type> *head, int k) 6 { 7 if(head == NULL || k < 1) 8 return NULL; 9 10 const ListNode<Type> *ahead, *after; 11 after = ahead = head; 12 13 for (int i = 0; i < k - 1; ++i) 14 { 15 //the list length less than k 16 if(ahead->next == NULL) 17 return NULL; 18 19 ahead = ahead->next; 20 } 21 22 while (ahead->next != NULL) 23 { 24 ahead = ahead->next; 25 after = after->next; 26 } 27 28 return after; 29 }
5. 合并两个有序链表
方法:新建一个首节点,然后遍历2个有序链表,比较大小,放在新链表后边
1 /** 2 * merge two sorted list 3 */ 4 template <typename Type> 5 ListNode<Type> * MergeTwoSortedList(ListNode<Type> *H1, ListNode<Type> *H2) 6 { 7 if (H1 == NULL) 8 return H2; 9 if (H2 == NULL) 10 return H1; 11 12 ListNode<Type> *head, *last; 13 14 head = new ListNode<Type>; 15 last = head; 16 17 while (H1 != NULL && H2 != NULL) 18 { 19 if (H1->data <= H2->data) 20 { 21 last->next = H1; 22 last = H1; 23 H1 = H1->next; 24 } 25 else 26 { 27 last->next = H2; 28 last = H2; 29 H2 = H2->next; 30 } 31 } 32 33 if (H1 != NULL) 34 last->next = H1; 35 else if (H2 != NULL) 36 last->next = H2; 37 38 H1 = head->next; 39 delete head; 40 41 return H1; 42 }
6. 求两个单链表的第一个公共结点
方法1:使用2个stack作为辅助空间,分别保存2个链表的遍历顺序,然后弹出,直到弹出的结点内容不同为止,上一个结点就是所求。
方法2:计算两个链表的长度len1和len2,分别用两个指针h1和h2指向两个链表的头,然后较长链表(假设链表1最长)的h1向后移动len2-len1个结点,然后同时向后移动h1和h2,直到h1=h2为止。
1 /** 2 * Find the first common node 3 */ 4 template <typename Type> 5 ListNode<Type> * Find1stCommonNode(ListNode<Type> *h1, ListNode<Type> *h2) 6 { 7 if(h1 == NULL || h2 == NULL) 8 return NULL; 9 10 int len1, len2; 11 12 len1 = GetListLength(h1); 13 len2 = GetListLength(h2); 14 15 if (len1 > len2) 16 { 17 for (int i = 0;i < len1 - len2; ++i) 18 h1 = h1->next; 19 } 20 else 21 { 22 for (int i = 0;i < len2 - len1; ++i) 23 h2 = h2->next; 24 } 25 26 while (h1 && h1 != h2) 27 { 28 h1 = h1->next; 29 h2 = h2->next; 30 } 31 32 return h1; 33 } 34 35 template <typename Type> 36 int GetListLength(const ListNode<Type> *head) 37 { 38 int num = 0; 39 40 while (head) 41 { 42 ++num; 43 head = head->next; 44 } 45 46 return num; 47 }
7.判断两个链表是否为Y型
方法:只需要判断最后一个结点是否相同即可。
1 /** 2 * judge two list crossing or not 3 */ 4 template <typename Type> 5 bool IsCrossing(ListNode<Type> *h1, ListNode<Type> *h2) 6 { 7 if(h1 == NULL || h2 == NULL) 8 return false; 9 10 while(h1->next != NULL) 11 h1 = h1->next; 12 while(h2->next != NULL) 13 h2 = h2->next; 14 15 if(h1 == h2) 16 return true; 17 return false; 18 }
8. 判断单链表是否存在环
判断单链表是否存在环的思想就是判断遍历的结点是否已经遍历过。
方法1:通过辅助空间来保存已经遍历过的结点,在每遍历一个结点时判断该结点是否已经在空间中,如果在就说明有环,否则把该结点写入辅助空间,直到找到环或访问链表结束。可以通过hashmap来保存访问的结点,查找效率是O(1)。但是需要O(n)的辅助空间。
方法2:通过两个指针,分别从链表的头结点出发,一个每次向后移动1步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。
1 /** 2 * judge the list has circle or not 3 */ 4 template <typename Type> 5 bool HasCircle(ListNode<Type> *head) 6 { 7 if(head == NULL) 8 return false; 9 10 ListNode<Type> *fast, *slow; 11 fast = slow = head; 12 13 while (fast && fast->next != NULL) 14 { 15 fast = fast->next->next; 16 slow = slow->next; 17 18 if(fast == slow) 19 return true; 20 } 21 22 return false; 23 }
9. 求链表的中间结点
如果链表的长度为偶数,返回中间两个结点的任意一个,若为奇数,则返回中间结点。
与求解倒数第k个结点的方法类似,可以通过两个指针来完成,不同的是,这里无法计算两个指针的距离,因此需要其中一个指针是快指针,另一个指针是慢指针。
1 /** 2 * get the middle node of list 3 */ 4 template <typename Type> 5 const ListNode<Type> * ListMidNode(const ListNode<Type> *head) 6 { 7 if(head == NULL) 8 return NULL; 9 10 const ListNode<Type> *fast, *slow; 11 fast = slow = head; 12 13 while(fast && fast->next != NULL) 14 { 15 fast = fast->next->next; 16 slow = slow->next; 17 } 18 19 return slow; 20 }
如果要求长度为偶数时,返回中间两个结点的第一个,将第13行改为如下条件:
while(fast && fast->next != NULL && fast->next->next != NULL)
以上是关于面试算法之链表(C++)的主要内容,如果未能解决你的问题,请参考以下文章