【题目描述】
一种特殊的单链表,节点结构如下所示
struct Node
{
int _value;
Node* _next;
Node* _rand;
Node(int val, Node*nx = nullptr, Node* rd = nullptr) : _value(val), _next(nx), _rand(rd) {}
Node(Node* other) : _value(other->_value), _next(other->_next), _rand(other->_rand) {}
};
除了next指针外还具有一个随机指向的rand指针,rand指针可能指向链表中的任一节点,也可能为null。给定一个由Node组成的无环单链表的头节点head,请实现一个函数完成对该链表的复制。
要求:时间复杂度O(N),额外空间复杂度O(1)
【对照组算法】
如果不要求额外空间复杂度,我们可以开辟一个map来保存新旧节点的对应关系,然后连接新链表。代码如下
Node* copyList(Node *head)
{
//创建一个哈希表
std::unordered_map<Node*, Node*> mapList;
//遍历传入的老链表,将老链表每个节点克隆出一个新节点,并存入对应的哈希表中
Node* temp = head;
while (nullptr != temp)
{
Node* newN = new Node(temp);
mapList[temp] = newN;
temp = temp->_next;
}
//遍历老链表,连接新链表
Node* newHead = mapList[head];
Node* newTemp = newHead;
temp = head;
while (nullptr != temp)
{
newTemp->_next = mapList[temp->_next];
newTemp->_rand = mapList[temp->_rand];
newTemp = newTemp->_next;
temp = temp->_next;
}
return newHead;
}
【测试用例参考】
int main(int argc, char *argv[])
{
Node n1(21);
Node n2(1,&n1);
Node n3(5, &n2);
Node n4(7, &n3);
Node n5(9, &n4);
Node n6(54, &n5);
Node n7(29, &n6);
Node n8(33, &n7);
n1._rand = &n7;
n3._rand = &n1;
n4._rand = &n6;
n8._rand = &n3;
Node *temp = &n8;
while (nullptr != temp)
{
cout << temp->_value;
if (nullptr != temp->_rand)
{
cout << "(" << temp->_rand->_value << ")";
}
cout << " ";
temp = temp->_next;
}
//拷贝链表
Node* newList = copyList(&n8);
cout << endl;
temp = newList;
while (nullptr != temp)
{
cout << temp->_value;
if (nullptr != temp->_rand)
{
cout << "(" << temp->_rand->_value << ")";
}
cout << " ";
temp = temp->_next;
}
system("pause");
return 0;
}
但是题目中要求空间复杂度为O(1),此算法的额外空间复杂度为O(N),不满足要求。
为什么要设置此对照组?因为正常情况下,这种算法思想是最快能想到的。在我们求职过程中,如果题目没有要求空间复杂度,可以用于在笔试中快速求解问题。
【算法描述】
本题的难点在于克隆的节点的rand指针无法在生成新链表节点同时确认出来;但是生成新链表后,新旧链表的节点(在不适用额外空间的情况下)不好进行一一对应,所以不方便通过旧链表节点的rand指针找到新链表节点的rand指针。
但是,如果我们想办法使新旧链表的对应节点能够一一对应起来,使找到旧节点计划能很快找到新节点,并且不影响遍历全部节点。就可以解决这个问题。
这里提供的算法思想是:将新节点连入旧链表中,使旧节点的next指针指向新节点,根据这一连接关系确认rand指针。即
旧链表:A -> B -> C -> D
连接成:A -> A' -> B -> B' -> C -> C' -> D -> D'
等确认完新链表的rand指针后,再进行分离。
【代码实现】
Node* copyListFun2(Node *head)
{
//1)遍历老链表,将老链表每个节点克隆出一个新节点,并放入对应老链表节点的next位置
//原链表:A->B->C->D
//新链表:A->A'->B->B'->C->C'->D->D'
Node* temp = head; //temp用于遍历原节点
while (nullptr != temp)
{
Node* newN = new Node(temp);
newN->_next = temp->_next;
temp->_next = newN;
temp = newN->_next;
}
//根据节点和克隆节点的位置关系,我们只要确认了原节点,就可以找到对应的克隆节点
//2)确认每个克隆节点的rand指针
temp = head; //temp用于遍历原节点
while (nullptr != temp)
{
if (nullptr != temp->_rand)
{
temp->_next->_rand = temp->_rand->_next;
}
temp = temp->_next->_next;
}
//分离原链表和克隆链表
Node* newHead = head->_next;
Node* newTemp = newHead; //temp用于遍历克隆节点
temp = head; //temp用于遍历原节点
while (nullptr != temp)
{
temp = temp->_next = newTemp->_next;
if (nullptr == temp)
{
break;
}
newTemp = newTemp->_next = temp->_next;
}
return newHead;
}