剑指offer - 删除链表的节点

题一

在O(1)时间内删除链表节点。给定单链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。

链表节点与函数的定义如下

struct ListNode {
    int m_nValue;
    ListNode *m_pNext;
};

void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted);

分析

思路1

在单向链表中删除一个节点,常规的做法无疑是从链表的头节点开始,顺序遍历查找要删除的节点,并在链表中删除该节点

如下图所示的链表中

272317431874277.jpg

我们想删除节点i,可以从链表的头节点a开始顺序遍历,发现节点hm_pNext指向要删除的节点i,于是我们可以把节点hm_pNext指向i的下一个节点,即节点j。指针调整之后,我们就可以安全地删除节点i并保证链表没有断开。如上图(b)所示,但是这种思路由于需要顺序查找,时间复杂度自然就是O(n)了。

之所以需要从头开始,是因为我们需要得到将被删除的节点的前一个节点。在单向链表中,节点中没有指向前一个节点的指针,所以只好从链表的头节点开始寻找。

思路2

其实可以很方便地得到要删除的节点的下一个节点。如果我们把下一个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点删除,那是不是就相当于把当前需要删除的节点删除了?

还是前面的例子,我们要删除节点i,先把i的下一个节点j的内容复制到i,然后把i的指针指向节点j的下一个节点。此时再删除j,其效果刚好是把节点i删除了,如图(c)所示

上述思路还有一个问题:如果要删除的节点位于链表的尾部,那么它就没有下一个节点,怎么办?我们仍然从链表的头节点开始,顺序遍历得到该节点的前序节点,并完成删除操作

最后需要注意的是,如果链表只有一个节点,而我们又要删除链表的头节点(也是尾节点),那么,此时我们在删除节点之后,还需要把链表的头节点设置为nullptr。

代码实现

void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted)
{
   if (!pListHead || !pToBeDeleted)
       return;

    // 要删除的节点不是尾节点
    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) // 链表只有一个节点,删除头节点(也是尾节点)
    {
        delete pToBeDeleted;
        pToBeDeleted = nullptr;
        *pListHead = nullptr;
    }
    else //链表中有多个节点,删除尾部节点
    {
        ListNode *pNode = *pListHead;
        while (pNode->m_pNext != pToBeDeleted) { // 顺序遍历,查找被删除节点
            pNode = pNode->m_pNext;
        }
        pNode->m_pNext = nullptr;
        delete pToBeDeleted;
        pToBeDeleted = nullptr;
    }
}

时间复杂度

对于n-1个非尾节点而言,我们可以在O(1)时间内把下一个节点的内存复制覆盖要删除的节点,并删除下一个节点;对于尾节点而言,由于仍然需要顺序查找,时间复杂度是O(n)。因此,总的平均复杂度是[(n-1)xO(1)+O(n)]/n,结果还是O(1)

但是注意:上述代码仍然是不完美的,因为它基于一个假设:要删除的节点的确在链表中。我们需要O(n)的时间才能判断链表中是否包含某一节点。受到O(1)时间的限制,我们不得不把确保点在链表中的责任推给了函数DeleteNode的调用者。

题目二

删除链表中的重复节点

在一个排序的链表中,如何删除重复的节点?例如:在下图(a)中重复的节点被删除之后,链表如图(b)

download.jpg

例如,链表1->2->3->3->4->4->5,处理后为 1->2->5

分析

解决这个问题的第一步是确定删除函数的参数。当然,这个函数需要输入待删除链表的头节点。头节点可能与后面的节点重复,也就是说头节点也可能被删除,因此删除函数应该声明为void DeleteDuplication(ListNode **pHead),而不是void DeleteDuplication(ListNode *pHead)。

接下来我们从头遍历整个链表,如果当前节点(代码中的pNode)的值与下一个节点的值相同,那么它们就是重复的节点,都应该被删除。

为了保证删除之后链表仍然相连的,我们要把当前节点的前一个节点(代码中的pNode)和后面值比当前节点的值大的节点相连。我们要确保pPreNode始终与下一个没有重复的节点连接在一起

以上图中的链表来分析删除重复节点的过程。

当我们遍历到第一个值为3的节点的时候,pPreNode指向值为2的节点。由于接下来的节点的值还是3,这两个节点应该被删除,因此pPreNode就和第一个值为4的节点相连。接下来由于值为4的两个节点也重复了,还是会被删除,所以pPreNode最终会和值为5的节点相连

代码实现

// 删除重复节点
void DeleteDuplication(ListNode **pHead)
{
    // 头节点为空,直接结束
    if (pHead == nullptr || *pHead == nullptr)
        return;

    ListNode *pPreNode = nullptr; // 记录上一个节点
    ListNode *pNode = *pHead;
    // 顺序遍历
    while (pNode != nullptr) {
        // 获取下一个节点
        ListNode *pNext = pNode->m_pNext;
        // 是否需要删除
        bool needDelete = false;
        // 当前节点值和下一个节点的值是否相等,相等标记该节点需要被删除 
        if (pNext != nullptr && pNext->m_nValue == pNode->m_nValue) {
            needDelete = true;
        }

        if (!needDelete) { // 不删除节点就移动指针
            pPreNode = pNode;
            pNode = pNode->m_pNext;
        }
        else // 当前节点需要删除
        {   
            int value = pNode->m_nValue;
            // 获得要删除的节点
            ListNode *pToBeDel = pNode;

            // 待删除节点不为空,并且值与需要删除的节点相当于,那么就一直循环进行,一直到节点的值不相等
            while (pToBeDel != nullptr && pToBeDel->m_nValue == value) {
                // 将待删除节点的下一个节点赋值给pNext
                pNext = pToBeDel->m_pNext;

                // 删除节点
                delete pToBeDel;
                pToBeDel = nullptr;

                // 重新作为待删除节点
                pToBeDel = pNext;
            }

            // 上一个节点是否存在
            if (pPreNode == nullptr)
                *pHead = pNext;
            else
                pPreNode->m_pNext = pNext;

            // 记录当前节点,继续查找下一个重复节点
            pNode = pNext;
        }
    }
}

参考

《剑指offer》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容

  • 最温暖的拆迁人。城市改造先锋! 脑袋中想要的公司的样子! 何谓温暖?给人以希望。 砥砺前行,做个决定,定下目标,前...
    浦大魔王76阅读 207评论 0 0
  • 烦。
    Vera微辣阅读 113评论 0 0
  • 『叮! 我总是会弄不清,什么是仪式感,什么是做作。 好像有的做作是种仪式,但又好像有的仪式却是种做作。 因为两者的...
    绵花不白阅读 182评论 0 0
  • 我有一壶酒 秦始皇抿了一口 挥鞭所指六国全收 我有一壶酒 曹雪芹抿了一口 红楼梦断千古风流 我有一壶酒 商鞅君抿了...
    高歌吟诗阅读 144评论 0 0
  • 晚上做了一个梦, 梦到自己像往常一样一个人去逛街, 看到街边卖橙子的两位相濡以沫的老人, 梦里一切都那么真切,连羡...
    白欢欢阅读 194评论 0 0