題一
在O(1)時(shí)間內(nèi)刪除鏈表節(jié)點(diǎn)。給定單鏈表的頭指針和一個(gè)節(jié)點(diǎn)指針,定義一個(gè)函數(shù)在O(1)時(shí)間內(nèi)刪除該節(jié)點(diǎn)。
鏈表節(jié)點(diǎn)與函數(shù)的定義如下
struct ListNode {
int m_nValue;
ListNode *m_pNext;
};
void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted);
分析
思路1
在單向鏈表中刪除一個(gè)節(jié)點(diǎn),常規(guī)的做法無(wú)疑是從鏈表的頭節(jié)點(diǎn)開(kāi)始,順序遍歷查找要?jiǎng)h除的節(jié)點(diǎn),并在鏈表中刪除該節(jié)點(diǎn)
如下圖所示的鏈表中
我們想刪除節(jié)點(diǎn)i
,可以從鏈表的頭節(jié)點(diǎn)a
開(kāi)始順序遍歷,發(fā)現(xiàn)節(jié)點(diǎn)h
的m_pNext
指向要?jiǎng)h除的節(jié)點(diǎn)i
,于是我們可以把節(jié)點(diǎn)h
的m_pNext
指向i
的下一個(gè)節(jié)點(diǎn),即節(jié)點(diǎn)j
。指針調(diào)整之后,我們就可以安全地刪除節(jié)點(diǎn)i并保證鏈表沒(méi)有斷開(kāi)。如上圖(b)所示,但是這種思路由于需要順序查找,時(shí)間復(fù)雜度自然就是O(n)了。
之所以需要從頭開(kāi)始,是因?yàn)槲覀冃枰玫綄⒈粍h除的節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)。在單向鏈表中,節(jié)點(diǎn)中沒(méi)有指向前一個(gè)節(jié)點(diǎn)的指針,所以只好從鏈表的頭節(jié)點(diǎn)開(kāi)始尋找。
思路2
其實(shí)可以很方便地得到要?jiǎng)h除的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。如果我們把下一個(gè)節(jié)點(diǎn)的內(nèi)容復(fù)制到需要?jiǎng)h除的節(jié)點(diǎn)上覆蓋原有的內(nèi)容,再把下一個(gè)節(jié)點(diǎn)刪除,那是不是就相當(dāng)于把當(dāng)前需要?jiǎng)h除的節(jié)點(diǎn)刪除了?
還是前面的例子,我們要?jiǎng)h除節(jié)點(diǎn)i
,先把i
的下一個(gè)節(jié)點(diǎn)j
的內(nèi)容復(fù)制到i
,然后把i
的指針指向節(jié)點(diǎn)j
的下一個(gè)節(jié)點(diǎn)。此時(shí)再刪除j
,其效果剛好是把節(jié)點(diǎn)i
刪除了,如圖(c)所示
上述思路還有一個(gè)問(wèn)題:如果要?jiǎng)h除的節(jié)點(diǎn)位于鏈表的尾部,那么它就沒(méi)有下一個(gè)節(jié)點(diǎn),怎么辦?我們?nèi)匀粡逆湵淼念^節(jié)點(diǎn)開(kāi)始,順序遍歷得到該節(jié)點(diǎn)的前序節(jié)點(diǎn),并完成刪除操作
最后需要注意的是,如果鏈表只有一個(gè)節(jié)點(diǎn),而我們又要?jiǎng)h除鏈表的頭節(jié)點(diǎn)(也是尾節(jié)點(diǎn)),那么,此時(shí)我們?cè)趧h除節(jié)點(diǎn)之后,還需要把鏈表的頭節(jié)點(diǎn)設(shè)置為nullptr。
代碼實(shí)現(xiàn)
void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted)
{
if (!pListHead || !pToBeDeleted)
return;
// 要?jiǎng)h除的節(jié)點(diǎn)不是尾節(jié)點(diǎn)
if (pToBeDeleted->m_pNext != nullptr)
{
ListNode *pNext = pToBeDeleted->m_pNext;
pToBeDeleted->m_nValue = pNext->m_nValue;
pToBeDeleted->m_pNext = pNext->m_pNext;
delete pNext;
pNext = nullptr;
}
else if (*pListHead == pToBeDeleted) // 鏈表只有一個(gè)節(jié)點(diǎn),刪除頭節(jié)點(diǎn)(也是尾節(jié)點(diǎn))
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
*pListHead = nullptr;
}
else //鏈表中有多個(gè)節(jié)點(diǎn),刪除尾部節(jié)點(diǎn)
{
ListNode *pNode = *pListHead;
while (pNode->m_pNext != pToBeDeleted) { // 順序遍歷,查找被刪除節(jié)點(diǎn)
pNode = pNode->m_pNext;
}
pNode->m_pNext = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
時(shí)間復(fù)雜度
對(duì)于n-1個(gè)非尾節(jié)點(diǎn)而言,我們可以在O(1)
時(shí)間內(nèi)把下一個(gè)節(jié)點(diǎn)的內(nèi)存復(fù)制覆蓋要?jiǎng)h除的節(jié)點(diǎn),并刪除下一個(gè)節(jié)點(diǎn);對(duì)于尾節(jié)點(diǎn)而言,由于仍然需要順序查找,時(shí)間復(fù)雜度是O(n)
。因此,總的平均復(fù)雜度是[(n-1)xO(1)+O(n)]/n
,結(jié)果還是O(1)
但是注意:上述代碼仍然是不完美的,因?yàn)樗谝粋€(gè)假設(shè):要?jiǎng)h除的節(jié)點(diǎn)的確在鏈表中。我們需要O(n)
的時(shí)間才能判斷鏈表中是否包含某一節(jié)點(diǎn)。受到O(1)
時(shí)間的限制,我們不得不把確保點(diǎn)在鏈表中的責(zé)任推給了函數(shù)DeleteNode
的調(diào)用者。
題目二
刪除鏈表中的重復(fù)節(jié)點(diǎn)
在一個(gè)排序的鏈表中,如何刪除重復(fù)的節(jié)點(diǎn)?例如:在下圖(a)中重復(fù)的節(jié)點(diǎn)被刪除之后,鏈表如圖(b)
例如,鏈表1->2->3->3->4->4->5,處理后為 1->2->5
分析
解決這個(gè)問(wèn)題的第一步是確定刪除函數(shù)的參數(shù)。當(dāng)然,這個(gè)函數(shù)需要輸入待刪除鏈表的頭節(jié)點(diǎn)。頭節(jié)點(diǎn)可能與后面的節(jié)點(diǎn)重復(fù),也就是說(shuō)頭節(jié)點(diǎn)也可能被刪除,因此刪除函數(shù)應(yīng)該聲明為void DeleteDuplication(ListNode **pHead)
,而不是void DeleteDuplication(ListNode *pHead)。
接下來(lái)我們從頭遍歷整個(gè)鏈表,如果當(dāng)前節(jié)點(diǎn)(代碼中的pNode
)的值與下一個(gè)節(jié)點(diǎn)的值相同,那么它們就是重復(fù)的節(jié)點(diǎn),都應(yīng)該被刪除。
為了保證刪除之后鏈表仍然相連的,我們要把當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)(代碼中的pNode)和后面值比當(dāng)前節(jié)點(diǎn)的值大的節(jié)點(diǎn)相連。我們要確保pPreNode
始終與下一個(gè)沒(méi)有重復(fù)的節(jié)點(diǎn)連接在一起
以上圖中的鏈表來(lái)分析刪除重復(fù)節(jié)點(diǎn)的過(guò)程。
當(dāng)我們遍歷到第一個(gè)值為3的節(jié)點(diǎn)的時(shí)候,pPreNode
指向值為2的節(jié)點(diǎn)。由于接下來(lái)的節(jié)點(diǎn)的值還是3,這兩個(gè)節(jié)點(diǎn)應(yīng)該被刪除,因此pPreNode
就和第一個(gè)值為4的節(jié)點(diǎn)相連。接下來(lái)由于值為4的兩個(gè)節(jié)點(diǎn)也重復(fù)了,還是會(huì)被刪除,所以pPreNode
最終會(huì)和值為5的節(jié)點(diǎn)相連
代碼實(shí)現(xiàn)
// 刪除重復(fù)節(jié)點(diǎn)
void DeleteDuplication(ListNode **pHead)
{
// 頭節(jié)點(diǎn)為空,直接結(jié)束
if (pHead == nullptr || *pHead == nullptr)
return;
ListNode *pPreNode = nullptr; // 記錄上一個(gè)節(jié)點(diǎn)
ListNode *pNode = *pHead;
// 順序遍歷
while (pNode != nullptr) {
// 獲取下一個(gè)節(jié)點(diǎn)
ListNode *pNext = pNode->m_pNext;
// 是否需要?jiǎng)h除
bool needDelete = false;
// 當(dāng)前節(jié)點(diǎn)值和下一個(gè)節(jié)點(diǎn)的值是否相等,相等標(biāo)記該節(jié)點(diǎn)需要被刪除
if (pNext != nullptr && pNext->m_nValue == pNode->m_nValue) {
needDelete = true;
}
if (!needDelete) { // 不刪除節(jié)點(diǎn)就移動(dòng)指針
pPreNode = pNode;
pNode = pNode->m_pNext;
}
else // 當(dāng)前節(jié)點(diǎn)需要?jiǎng)h除
{
int value = pNode->m_nValue;
// 獲得要?jiǎng)h除的節(jié)點(diǎn)
ListNode *pToBeDel = pNode;
// 待刪除節(jié)點(diǎn)不為空,并且值與需要?jiǎng)h除的節(jié)點(diǎn)相當(dāng)于,那么就一直循環(huán)進(jìn)行,一直到節(jié)點(diǎn)的值不相等
while (pToBeDel != nullptr && pToBeDel->m_nValue == value) {
// 將待刪除節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)賦值給pNext
pNext = pToBeDel->m_pNext;
// 刪除節(jié)點(diǎn)
delete pToBeDel;
pToBeDel = nullptr;
// 重新作為待刪除節(jié)點(diǎn)
pToBeDel = pNext;
}
// 上一個(gè)節(jié)點(diǎn)是否存在
if (pPreNode == nullptr)
*pHead = pNext;
else
pPreNode->m_pNext = pNext;
// 記錄當(dāng)前節(jié)點(diǎn),繼續(xù)查找下一個(gè)重復(fù)節(jié)點(diǎn)
pNode = pNext;
}
}
}
參考
《劍指offer》