本博文着重實現《劍指Offer》上面的單向鏈表操作。
//數據結構
struct ListNode
{
int data;
ListNode *next;
ListNode(int x) :data(x), next(NULL) {}
};
//在鏈表尾部添加結點
void AddToTail(ListNode **pHead, int value)
{
ListNode *pNew = new ListNode(value);
assert(pNew != NULL);
if (NULL == *pHead)//原鏈表爲空
*pHead = pNew;
else
{
ListNode *pNode = *pHead;
while (pNode->next)
pNode = pNode->next;
pNode->next = pNew;
}
}
/*單向鏈表刪除操作需要找到前一個結點
二級指針操作是因爲會可能修改頭結點指針*/
void RemoveNode(ListNode **pHead, int value)
{
if (NULL == pHead || NULL == *pHead)
return;
ListNode *pToDeleted = NULL;
if ((*pHead)->data == value)
{
pToDeleted = *pHead;
*pHead = (*pHead)->next;
}
else
{
ListNode *pNode = *pHead;
while (pNode->next && pNode->next->data != value)//找到待刪除結點的前結點
pNode = pNode->next;
if (pNode->next && pNode->next->data == value)
{
pToDeleted = pNode->next;
pNode->next = pNode->next->next;
}
}
if (pToDeleted != NULL)
{
delete pToDeleted;
pToDeleted = NULL;
}
}
//反向打印:遞歸。鏈表過長會導致棧溢出
void PrintReverse(ListNode *pHead)
{
if (pHead)
{
PrintReverse(pHead->next);
cout << pHead->data << endl;
}
}
//反向打印:棧
void PrintReverse_iteratively(ListNode *pHead)
{
stack<ListNode *> nodes;
ListNode *pNode = pHead;
while (pNode)
{
nodes.push(pNode);
pNode = pNode->next;
}
while (!nodes.empty())
{
pNode = nodes.top();
cout << pNode->data << endl;
nodes.pop();
}
}
/*
給定單向鏈表的頭指針和一個結點指針,定義一個函數在O(1)時間刪除該結點
前提是這個結點是存在於這個鏈表中,這個由調用者保證
其實現思想:把下一個結點的內容複製到待刪除結點上覆蓋原有的內容,然後刪除下一個結點。
就是操作結點的數據而不是鏈表的鏈接關係。平衡二叉樹的平衡旋轉也是採用這個思想
*/
void DeleteNode(ListNode **pHead, ListNode *pToBeDeleted)
{
if (NULL == pHead || NULL == *pHead)
return;
//要刪除的結點不是尾節點
if (pToBeDeleted->next)
{
ListNode *pNext = pToBeDeleted->next;
pToBeDeleted->data = pNext->data;//操作數據而非鏈接關係
pToBeDeleted->next = pNext->next;
delete pNext;
pNext = NULL;
}
//鏈表只有一個結點
else if (*pHead == pToBeDeleted)
{
delete pToBeDeleted;
pToBeDeleted = NULL;
*pHead = NULL;
}
//鏈表有多個結點,且待刪除結點是尾節點
else//這部分時間複雜度爲O(n),但總的平均時間複雜度爲O(1)
{
ListNode *pNode = *pHead;
while (pNode->next != pToBeDeleted)
pNode = pNode->next;
pNode->next = NULL;
delete pToBeDeleted;
pToBeDeleted = NULL;
}
}
/*輸入一個鏈表,輸出該鏈表中倒數第 k 個結點
思路一:棧*/
ListNode* PrintKNode(ListNode *pHead, int k)
{
if (NULL == pHead || k < 1)
return NULL;
stack<ListNode *> nodes;
ListNode *pNode = pHead;
while (pNode)
{
nodes.push(pNode);
pNode = pNode->next;
}
while (!nodes.empty())
{
--k;
if (0 == k)
{
pNode = nodes.top();
return pNode;
}
nodes.pop();
}
return NULL;
}
/*思路二:快慢指針。快慢指針之間的距離爲k-1,當快指針到達尾結點時,慢指針恰好是倒數第k個結點*/
ListNode* FindKthToTail(ListNode *pHead, int k)
{
if (NULL == pHead || k < 1)
return NULL;
ListNode *pAhead = pHead;
ListNode *pBehind = NULL;
for (int i = 0; i < k - 1; ++i)
{
if (pAhead->next)
pAhead = pAhead->next;
else
return NULL;
}
pBehind = pHead;
while (pAhead->next)
{
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}
/*反轉鏈表:輸入一個鏈表的頭結點,反轉該鏈表並輸出反轉後鏈表的頭結點
充分考慮異常情況,尤其是清楚最後反轉後的頭結點*/
ListNode* ReverseList(ListNode *pHead)
{
if (NULL == pHead || pHead->next == NULL)
return pHead;
ListNode *pPrev = NULL;
ListNode *pNode = pHead;
while (pNode)
{
ListNode *pNext = pNode->next;
pNode->next = pPrev;
pPrev = pNode;//這就是最後反轉後的頭結點
pNode = pNext;
}
return pPrev;
}
/*合併兩個排序的鏈表,合併後的鏈表仍是排序好的(遞增)*/
/*未排序區的兩個鏈表值小的那個頭結點,將是未排序區合併後的頭結點,同時也將作爲排序區的尾結點。
以此遞歸,每次只需比較剩下未排序的兩個鏈表的頭結點*/
ListNode* MergeList(ListNode *pHead1, ListNode *pHead2)
{
if (NULL == pHead1)
return pHead2;
else if (NULL == pHead2)
return pHead1;
ListNode *pMergedHead = NULL;
if (pHead1->data < pHead2->data)
{
pMergedHead = pHead1;
pMergedHead->next = MergeList(pHead1->next, pHead2);
}
else
{
pMergedHead = pHead2;
pMergedHead->next = MergeList(pHead1, pHead2->next);
}
return pMergedHead;
}
/*輸入兩個鏈表,找出它們的第一個公共結點
下面鏈表L1有6個結點,L2有4個結點,其第一個公共結點爲mn1*/
/*
L1: n1 -> n2 -> n3 -> n4
\
——> mn1 -> mn2
/
L2: m1 -> m2
*/
/*
實現思想:長的鏈表先前進若干結點到與短鏈表等長位置,然後同步前進
充分考慮異常情況
*/
int GetLength(ListNode *pHead)
{
int leng = 0;
while (pHead)
{
leng++;
pHead = pHead->next;
}
return leng;
}
ListNode* FindFirstCommonNode(ListNode *L1, ListNode *L2)
{
if (NULL == L1 || NULL == L2)
return NULL;
int leng1 = GetLength(L1);
int leng2 = GetLength(L2);
int distance = (leng1 > leng2) ? leng1 - leng2 : leng2 - leng1;
ListNode *pFirst = NULL, *pSecond = NULL;
if (leng1 > leng2)
{
pFirst = L1;
pSecond = L2;
}
else
{
pFirst = L2;
pSecond = L1;
}
while (distance--)//考慮非公共部分長度一樣情況
{
pFirst = pFirst->next;
}
while (pFirst && pSecond)
{
if (pFirst == pSecond)//尋找第一個公共結點
return pFirst;
pFirst = pFirst->next;
pSecond = pSecond->next;
}
return NULL;//不存在公共結點情況
}
/*鏈表中環的入口結點
最常規解法:快慢指針,關鍵在於定位環的入口結點,
具體分析參見博文:http://blog.csdn.net/wenqian1991/article/details/17452715 */
ListNode* EntryNodeOfLoop(ListNode *pHead)
{
if (NULL == pHead)
return NULL;
ListNode *pFast = pHead;
ListNode *pSlow = pHead;
//判斷是否有環
while (pFast->next)//兩種情況跳出循環
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if (NULL == pFast)
return NULL;
if (pFast == pSlow)//有環
break;
}
if (NULL == pFast->next)//沒環
return NULL;
//尋找環的入口結點
/*快指針移動至鏈表頭結點,同時兩結點以相同速度再次相遇就是環的開始節點
http://blog.csdn.net/wenqian1991/article/details/17452715 */
pFast = pHead;
while (pFast != pSlow)
{
pFast = pFast->next;
pSlow = pSlow->next;
}
return pFast;
}