劍指offer - 刪除鏈表的節(jié)點(diǎn)

題一

在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)

如下圖所示的鏈表中

272317431874277.jpg

我們想刪除節(jié)點(diǎn)i,可以從鏈表的頭節(jié)點(diǎn)a開(kāi)始順序遍歷,發(fā)現(xiàn)節(jié)點(diǎn)hm_pNext指向要?jiǎng)h除的節(jié)點(diǎn)i,于是我們可以把節(jié)點(diǎn)hm_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)

download.jpg

例如,鏈表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》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • 最溫暖的拆遷人。城市改造先鋒! 腦袋中想要的公司的樣子! 何謂溫暖?給人以希望。 砥礪前行,做個(gè)決定,定下目標(biāo),前...
    浦大魔王76閱讀 210評(píng)論 0 0
  • 煩。
    Vera微辣閱讀 169評(píng)論 0 0
  • 『叮! 我總是會(huì)弄不清,什么是儀式感,什么是做作。 好像有的做作是種儀式,但又好像有的儀式卻是種做作。 因?yàn)閮烧叩?..
    綿花不白閱讀 184評(píng)論 0 0
  • 我有一壺酒 秦始皇抿了一口 揮鞭所指六國(guó)全收 我有一壺酒 曹雪芹抿了一口 紅樓夢(mèng)斷千古風(fēng)流 我有一壺酒 商鞅君抿了...
    高歌吟詩(shī)閱讀 146評(píng)論 0 0
  • 晚上做了一個(gè)夢(mèng), 夢(mèng)到自己像往常一樣一個(gè)人去逛街, 看到街邊賣橙子的兩位相濡以沫的老人, 夢(mèng)里一切都那么真切,連羨...
    白歡歡閱讀 206評(píng)論 0 0