总结:复杂链表的复制

题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下:

struct ComplexNode{

   int m_nValue;

   ComplexNode* m_pNext;

   ComplexNode* m_pSibling;

};
               下图是一个含有 5 个结点的该类型复杂链表。图中实线箭头表示 m_pNext 指针,虚线箭头表示 m_pSibling 指针。为简单起见,指向 NULL 的指针没有画出。

 

                 程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题

请完成函数ComplexNode*Clone(ComplexNode* pHead),在o(n)时间复杂度内复制一个复杂链表。

               分析:在常见的数据结构上稍加变化,这是一种很新颖的面试题。要在不到一个小时的时间里解决这种类型的题目,我们需要较快的反应能力,对数据结构透彻的理解以及扎实的编程功底。


           看到这个问题,我的第一反应是分成两步:第一步是复制原始链表上的每个链表,并用m_pNext链接起来。第二步,假设原始链表中的某节点Nm_pSibling指向结点S。由于S的位置在链表上有可能在N的前面也可能在N的后面,所以要定位N的位置我们需要从原始链表的头结点开始找。假设从原始链表的头结点开始经过s步找到结点S。那么在复制链表上结点Nm_pSiblingS’,离复制链表的头结点的距离也是s。用这种办法我们就能为复制链表上的每个结点设置m_pSibling了。

            对一个含有n个结点的链表,由于定位每个结点的m_pSibling,都需要从链表头结点开始经过O(n)步才能找到,因此这种方法的总时间复杂度是O(n2)

            由于上述方法的时间主要花费在定位结点的m_pSibling上面,我们试着在这方面去做优化。我们还是分为两步:第一步仍然是复制原始链表上的每个结点N,并创建N’,然后把这些创建出来的结点链接起来。这里我们对<NN’>的配对信息放到一个哈希表中。第二步还是设置复制链表上每个结点的m_pSibling。如果在原始链表中结点Nm_pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。由于有了哈希表,我们可以用O(1)的时间根据S找到S’

            第二种方法相当于用空间换时间,以O(n)的空间消耗实现了O(n)的时间效率。

            接着我们来换一种思路,在不用辅助空间的情况下实现O(n)的时间效率。第三种方法的第一步仍然是根据原始链表的每个结点N,创建对应的N’。这一次,我们把N’链接在N的后面。实例中的链表经过这一步之后变成了:

                程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题

                这一步的代码如下: 

/

// Clone all nodes in a complex linked list with head pHead,

// and connect all nodes with m_pNext link

/

void CloneNodes(ComplexNode* pHead)

{

    ComplexNode* pNode = pHead;

    while(pNode != NULL)

    {

        ComplexNode* pCloned = new ComplexNode();

        pCloned->m_nValue = pNode->m_nValue;

        pCloned->m_pNext = pNode->m_pNext;

        pCloned->m_pSibling = NULL;

 

        pNode->m_pNext = pCloned;

 

        pNode = pCloned->m_pNext;

    }

}
            第二步是设置我们复制出来的链表上的结点的m_pSibling。假设原始链表上的Nm_pSibling指向结点S,那么其对应复制出来的程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题N’N->m_pNext,同样S’也是S->m_pNext。这就是我们在上一步中把每个结点复制出来的结点链接在原始结点后面的原因。有了这样的链接方式,我们就能在O(1)中就能找到每个结点的m_pSibling了。例子中的链表经过这一步,就变成如下结构了:

             程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题

 这一步的代码如下:

/

// Connect sibling nodes in a complex link list

/

void ConnectSiblingNodes(ComplexNode* pHead)

{

    ComplexNode* pNode = pHead;

    while(pNode != NULL)

    {

        ComplexNode* pCloned = pNode->m_pNext;

        if(pNode->m_pSibling != NULL)

        {

            pCloned->m_pSibling = pNode->m_pSibling->m_pNext;

        }

 

        pNode = pCloned->m_pNext;

    }

}
                第三步是把这个长链表拆分成两个:把奇数位置的结点链接起来就是原始链表,把偶数位置的结点链接出来就是复制出来的链表。上述例子中的链表拆分之后的两个链表如下:

                程序员面试题精选100题(49)-复杂链表的复制 - 何海涛 - 微软、Google等面试题

                要实现这一步,也不是很难的事情。其对应的代码如下:

/

// Split a complex list into two:

// Reconnect nodes to get the original list, and its cloned list

/

ComplexNode* ReconnectNodes(ComplexNode* pHead)

{

    ComplexNode* pNode = pHead;

    ComplexNode* pClonedHead = NULL;

    ComplexNode* pClonedNode = NULL;

 

    if(pNode != NULL)

    {

        pClonedHead = pClonedNode = pNode->m_pNext;

        pNode->m_pNext = pClonedNode->m_pNext;

        pNode = pNode->m_pNext;

    }

 

    while(pNode != NULL)

    {

        pClonedNode->m_pNext = pNode->m_pNext;

        pClonedNode = pClonedNode->m_pNext;

 

        pNode->m_pNext = pClonedNode->m_pNext;

        pNode = pNode->m_pNext;

    }

 

    return pClonedHead;

}


其实有个很巧妙的方法, 给A,B,C,D,E编号,分别为0,1,2,3,4.如果所示. 这样m_pSibling的clone就很简单.
Forexample:
    节点 b 对应的是原节点B, B->m_pSibling 是E, E的编号是4.这样新列表的第4个节点的地址赋给b->m_pSibling就可以了.
但关键一点是原列表每个节点的编号保存在哪呢?如果编号没有地方保存, 也不能降低clone的复杂度. 我们注意到没每个节电有一个m_nValue成员, 我们可以利用它来做文章.即首先把每个节电的m_nValue备份出来, 临时用来保存编号, 等clone完成后, 再restore.
同时还要把a,b,c.d,e的地址也按顺序保存在一个数组里,以方便获取他们的地址.

精选软件工程师面试题-复杂链表的复制


这样这个算法就很简单了,只对原列表遍历两次就可以完成复制, 时间复杂度为O(2n):
  1. 备份原列表每个节点的 m_nValue 到数组backupValue. 同时分别设置他们为所在节点的序号值.
  2. 由原列表 clone出新列表(只设置每个接点的m_pNext值). 新列表每个节点的m_pSibling 置为 NULL.同时新列标的每个节点地址按顺序保留到数组newListAddrees中.
  3. Clone m_pSibling:从原列表取一个节点, 如果此节点的m_pSibling为NULL, 取下一个.假设此节点为M. 由M->m_nValue 找到 m(newListAddrees[M->m_nValue]). 并设置m_pSibling:newListAddrees[M->m_nValue]->m_pSibling=newListAddrees[M->m_pSibling->m_nValue].直到原列表的节点的m_pSibling全复制完.
  4. 由backupValue恢复原列表的m_nValue.
  5. 设置新列表的m_nValue.
代码:
ComplexNode* CloneList(ComplexNode* pHead)
    {
       ComplexNode*  pNode = pHead;
       ComplexNode** ppBackupNewList;
       int          nTotal = 0;
       int          i = 0;
       while(pNode)
       {
           nTotal++;
           pNode = pNode->m_pNext;
       }
       ppBackupNewList = (ComplexNode **)malloc(sizeof(ComplexNode *)*nTotal);
   
       pNode = pHead;
       while(pNode != NULL)
       {
           ppBackupNewList[i] = (ComplexNode *)malloc(sizeof(ComplexNode));
           if (i > 0)
               ppBackupNewList[i - 1]->m_pNext =ppBackupNewList[i];
           ppBackupNewList[i]->m_pSibling = NULL;
           ppBackupNewList[i]->m_nValue =pNode->m_nValue;
           pNode->m_nValue = i++;
           pNode = pNode->m_pNext;
       }
       ppBackupNewList[nTotal - 1]->m_pNext = NULL;
   
       pNode = pHead;
       while(pNode != NULL)
       {
           if (pNode->m_pSibling)
               ppBackupNewList[pNode->m_nValue]->m_pSibling=
                                ppBackupNewList[pNode->m_pSibling->m_nValue];
           pNode = pNode->m_pNext;
       }
   
       i = 0;
       pNode = pHead;
       while(pNode != NULL)
       {
           pNode->m_nValue =ppBackupNewList[i++]->m_nValue;
           pNode = pNode->m_pNext;
       }
   
       pNode = ppBackupNewList[0];
       free(ppBackupNewList);
   
       return pNode;
   }




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值