面试题 37,两个链表的第一个公共节点(附引申:环链表的环入口节点) (纪念第一次.txt 白板写代码 error free bug free)
想了五分钟没想出好的解法,最优解其实很简单,自己也已经摸到门口了,最后没想下去,蠢死了。。
思路:首先,从公共节点开始,两条链表肯定已经合并了(这个已经想到了),定义两个指针变量两个链表,直到某一个指针到达末端(这个也想到了)。这样就计算出了两个链表的长度差,重新遍历,较长的链表上的指针提前出发,把长度差走掉,然后两个指针地址相等时就是第一个公共节点了。
时间复杂度(M+N),无额外空间。
自己代码,比书上要简洁一些,不需要计数,另定义指针p,和那个没走完的指针同步行走直到没走完的指针走到底了,p停下的位置就是该提前走掉的长度差。
#include <stdio.h> #include <stdlib.h> struct ListNode { int m_nValue; ListNode* m_pNext; }; //==================算法代码===================== ListNode *FindFirstCommon(ListNode *list1, ListNode *list2){ if(NULL == list1 || NULL == list2) return NULL; ListNode* p1 = list1; ListNode* p2 = list2; while(p1 != NULL && p2 != NULL){ p1 = p1 -> m_pNext; p2 = p2 -> m_pNext; } ListNode* p = NULL; if(p1 == NULL){ p = list2; for(; p2 != NULL; p2 = p2 -> m_pNext, p = p -> m_pNext); for(p1 = list1; p1 != NULL && p != NULL && p != p1; p1 = p1 -> m_pNext, p = p -> m_pNext); }else{ p = list1; for(; p1 != NULL; p1 = p1 -> m_pNext, p = p -> m_pNext); for(p2 = list2; p2 != NULL && p != NULL && p != p2; p2 = p2 -> m_pNext, p = p -> m_pNext); } if(p != NULL) return p; return NULL; } // ====================链表构造代码==================== ListNode* CreateListNode(int value) { ListNode* pNode = new ListNode(); pNode->m_nValue = value; pNode->m_pNext = NULL; return pNode; } void ConnectListNodes(ListNode* pCurrent, ListNode* pNext) { if(pCurrent == NULL) { printf("Error to connect two nodes.\n"); exit(1); } pCurrent->m_pNext = pNext; } void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } void PrintList(ListNode* pHead) { printf("PrintList starts.\n"); ListNode* pNode = pHead; while(pNode != NULL) { printf("%d\t", pNode->m_nValue); pNode = pNode->m_pNext; } printf("\nPrintList ends.\n"); } void DestroyList(ListNode* pHead) { ListNode* pNode = pHead; while(pNode != NULL) { pHead = pHead->m_pNext; delete pNode; pNode = pHead; } } void AddToTail(ListNode** pHead, int value) { ListNode* pNew = new ListNode(); pNew->m_nValue = value; pNew->m_pNext = NULL; if(*pHead == NULL) { *pHead = pNew; } else { ListNode* pNode = *pHead; while(pNode->m_pNext != NULL) pNode = pNode->m_pNext; pNode->m_pNext = pNew; } } void RemoveNode(ListNode** pHead, int value) { if(pHead == NULL || *pHead == NULL) return; ListNode* pToBeDeleted = NULL; if((*pHead)->m_nValue == value) { pToBeDeleted = *pHead; *pHead = (*pHead)->m_pNext; } else { ListNode* pNode = *pHead; while(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue != value) pNode = pNode->m_pNext; if(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue == value) { pToBeDeleted = pNode->m_pNext; pNode->m_pNext = pNode->m_pNext->m_pNext; } } if(pToBeDeleted != NULL) { delete pToBeDeleted; pToBeDeleted = NULL; } } // ====================测试代码==================== void DestroyNode(ListNode* pNode); void Test(char* testName, ListNode* pHead1, ListNode* pHead2, ListNode* pExpected) { if(testName != NULL) printf("%s begins: ", testName); ListNode* pResult = FindFirstCommon(pHead1, pHead2); if(pResult == pExpected) printf("Passed.\n"); else printf("Failed.\n"); } // 第一个公共结点在链表中间 // 1 - 2 - 3 \ // 6 - 7 // 4 - 5 / void Test1() { ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ListNode* pNode6 = CreateListNode(6); ListNode* pNode7 = CreateListNode(7); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode6); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode6); ConnectListNodes(pNode6, pNode7); Test("Test1", pNode1, pNode4, pNode6); DestroyNode(pNode1); DestroyNode(pNode2); DestroyNode(pNode3); DestroyNode(pNode4); DestroyNode(pNode5); DestroyNode(pNode6); DestroyNode(pNode7); } // 没有公共结点 // 1 - 2 - 3 - 4 // // 5 - 6 - 7 void Test2() { ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ListNode* pNode6 = CreateListNode(6); ListNode* pNode7 = CreateListNode(7); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode5, pNode6); ConnectListNodes(pNode6, pNode7); Test("Test2", pNode1, pNode5, NULL); DestroyList(pNode1); DestroyList(pNode5); } // 公共结点是最后一个结点 // 1 - 2 - 3 - 4 \ // 7 // 5 - 6 / void Test3() { ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ListNode* pNode6 = CreateListNode(6); ListNode* pNode7 = CreateListNode(7); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode7); ConnectListNodes(pNode5, pNode6); ConnectListNodes(pNode6, pNode7); Test("Test3", pNode1, pNode5, pNode7); DestroyNode(pNode1); DestroyNode(pNode2); DestroyNode(pNode3); DestroyNode(pNode4); DestroyNode(pNode5); DestroyNode(pNode6); DestroyNode(pNode7); } // 公共结点是第一个结点 // 1 - 2 - 3 - 4 - 5 // 两个链表完全重合 void Test4() { ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); Test("Test4", pNode1, pNode1, pNode1); DestroyList(pNode1); } // 输入的两个链表有一个空链表 void Test5() { ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); Test("Test5", NULL, pNode1, NULL); DestroyList(pNode1); } // 输入的两个链表有一个空链表 void Test6() { Test("Test6", NULL, NULL, NULL); } void DestroyNode(ListNode* pNode) { delete pNode; pNode = NULL; } int main() { Test1(); Test2(); Test3(); Test4(); Test5(); Test6(); return 0; }
纪念第一次用 .txt 白板写出的代码不改一字 编译测试通过。
书上代码:
unsigned int GetListLength(ListNode* pHead); ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { // 得到两个链表的长度 unsigned int nLength1 = GetListLength(pHead1); unsigned int nLength2 = GetListLength(pHead2); int nLengthDif = nLength1 - nLength2; ListNode* pListHeadLong = pHead1; ListNode* pListHeadShort = pHead2; if(nLength2 > nLength1) { pListHeadLong = pHead2; pListHeadShort = pHead1; nLengthDif = nLength2 - nLength1; } // 先在长链表上走几步,再同时在两个链表上遍历 for(int i = 0; i < nLengthDif; ++ i) pListHeadLong = pListHeadLong->m_pNext; while((pListHeadLong != NULL) && (pListHeadShort != NULL) && (pListHeadLong != pListHeadShort)) { pListHeadLong = pListHeadLong->m_pNext; pListHeadShort = pListHeadShort->m_pNext; } // 得到第一个公共结点 ListNode* pFisrtCommonNode = pListHeadLong; return pFisrtCommonNode; } unsigned int GetListLength(ListNode* pHead) { unsigned int nLength = 0; ListNode* pNode = pHead; while(pNode != NULL) { ++ nLength; pNode = pNode->m_pNext; } return nLength; }
引申:与此类似还有一道非常常见的题目
(1) 判断链表中是否有环 (2) 如果有,寻找环的入口
(1) 对于第一问,最常见的解法自然就是两个指针一个步长1,一个步长2,步长1到达NULL之前相遇,则有环。
(2) 第二问的答案来自<Cracking the Coding Interview 4ed> Chapter 2.5
要想找到环的入口,先用方法(1)判断有环,然后一个指针从头开始走,另一个指针从相遇点继续走,两个指针步长都是1,两个指针又相遇时就是环的入口。实现很简单。为什么这样就可以呢?下面给出自己的证明过程:
设链表直线部分长X,环部分长Y。
第一次相遇前,步长1的指针走过m距离,步长2的指针走过2m距离。并且我们可以知道,步长1的指针在环里走不超过一圈就会被步长2的指针撞上。而步长2的指针可能在步长1指针进环之前已经绕了很多圈。
我们可以得出等式,相遇时: 2m - m = nY,n 是正整数,也就是说两个指针走过的路程差肯定是整数倍的环长度。此时要想继续往下走到环入口,还有Y - (m - X)的距离。
接着一个从头走,一个继续走,要证明第二次相遇就是环入口,我们只要证明 X 和 Y - (m - X) 的差是Y的整数倍即可。
即证明存在整数 k >= 0,使得X = Y - (m-X) + kY
带入之前的 m = nY得 X = X + (k-n+1)Y
当k = n-1 时,可以让两个指针在入口相遇。因此解法成立。
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2014-03-02 02:12 Felix Fang 阅读(977) 评论(0) 编辑 收藏 举报