【小白爬Leetcode138】1.7 复制带随机指针的链表 Copy List with Random Pointer


Leetcode 138 m e d i u m \color{#FF7D00}{medium} medium

点击进入原题链接:LeetCode中国站

题目

做题之前:
如果对DeepCopy 深拷贝不理解的读者,需要补一下这方面的知识,或者看懂我的这句话:
一个变量 a 对应着一块内存空间,如果我现在要拷贝这个变量a,那么我有两种选择:
1.浅拷贝:新建一个变量b,指向变量a的内存空间的地址。这样&a == &b,当变量a发生改变的时候,b也会相应改变(因为二者指向的都是同一块内存空间);一般默认的赋值运算符=也是默认浅拷贝。
2.深拷贝:新建一个变量b,另开辟一块内存空间,把a的值拷贝过来。这样&a != &b,当变量a发生改变的时候,b是不会收到影响的。

Description

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

中文描述

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路一 用map映射

try1:构建两个地址:序号的map

C++中的 map 和Python中的 dict 相似,都是储存键值对映射关系的数据结构。
想法
这道题的难点在于:由于链表不可以随机访问,只能遍历访问,那么如何拷贝random指针?难道每一个节点的random的确定都要都要遍历一次链表?这样时间复杂度就变成了可怕的O(n2)。

我们不妨牺牲一些空间复杂度,用一个map容器来储存原链表和新链表的节点地址:序号,如0x00AA:0 表示0x00AA这个内存储存的是第0个节点。
这样我们总共需要遍历两次链表:
第一个while循环:
做三件事:
  1.构建原来链表地址:位置的map;
  2.构建新链表的地址:位置的map;
  3.构建新链表的顺序结构。

第二个while循环:
做三件事:
  1.找出原链表random指针的index; 直接 map[key] 即可
  2.通过index找出新链表中的地址 ;需要遍历map来获得
  3.构建新链表的random。

完整代码如下:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head){return NULL;}

        std::map<Node*,int> node_map_origin;
        std::map<Node*,int> node_map_new;
        int index = 0;

        Node* cur_origin = head;
        Node* cur_new = new Node(cur_origin->val);
        Node* head_new = cur_new;  //记录下新链表的开头

        //第一遍遍历原链表,做三件事:
        while(cur_origin)
        {
            //1.构建原来链表地址:位置的map;
            node_map_origin[cur_origin] = index;
            //2.构建新链表的地址:位置的map;  
            node_map_new[cur_new] = index;
            //3.构建新链表的顺序结构;
            if(cur_origin->next)
            {
                cur_new->next = new Node(cur_origin->next->val);
            }
            else{cur_new->next = NULL;}
            //4.继续向下遍历原链表,注意新链表也得向下遍历
            cur_origin = cur_origin->next;
            cur_new = cur_new->next;
            //5.位置加一
            index++;
        }
        //第二遍遍历原链表,做三件事:
        cur_origin = head;
        cur_new = head_new;
        while(cur_new)
        {
            //1.找出原链表random指针的index
            Node* ptr = NULL;
            if(cur_origin->random)  //如果random不为NULL;
            {
                //找到index
                index = node_map_origin[cur_origin->random];
             //2.通过index找出新链表中的地址
                for(std::map<Node*,int>::iterator it = node_map_new.begin();it!=node_map_new.end();it++)
                {
                    if(it->second == index)
                    {
                        ptr = it->first;
                    }
                }
            }
            //3.构建新链表的random
            cur_new->random = ptr;

            cur_new= cur_new->next;
            cur_origin = cur_origin->next;
        }
        return head_new;
    }
}; 

时间复杂度:O(n); 空间复杂度O(n)

结果惨淡,应该是哪里不巧妙。
在这里插入图片描述

try2:map1映射地址:序号,map2映射序号:地址

方法一的速度为什么这么慢呢?试图寻找答案:看了别人的思路,发现我在构建map的时候存在一个问题:
在node_map_new中通过序号找地址的时候,相当于是通过value来找key,这样需要遍历字典,不够简洁。换了一下map的结构:
node_map_origin 是地址:序号的形式,而map2中是序号:地址的形式,这样两次查找都可以直接用map的特性,即通过key找value:
修改后的源码如下:

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head){return NULL;}

        std::map<Node*,int> node_map_origin;
        std::map<int,Node*> node_map_new;
        int index = 0;

        Node* cur_origin = head;
        Node* cur_new = new Node(cur_origin->val);
        Node* head_new = cur_new;  //记录下新链表的开头

        //第一遍遍历原链表,做三件事:
        while(cur_origin)
        {
            //1.构建原来链表地址:位置的map;
            node_map_origin[cur_origin] = index;
            //2.构建新链表的地址:位置的map;  
            node_map_new[index] = cur_new;
            //3.构建新链表的顺序结构;
            if(cur_origin->next)
            {
                cur_new->next = new Node(cur_origin->next->val);
            }
            else{cur_new->next = NULL;}
            //4.继续向下遍历原链表,注意新链表也得向下遍历
            cur_origin = cur_origin->next;
            cur_new = cur_new->next;
            //5.位置加一
            index++;
        }
        //第二遍遍历原链表,做三件事:
        cur_origin = head;
        cur_new = head_new;
        while(cur_new)
        {
            //1.找出原链表random指针的index
            Node* ptr = NULL;
            if(cur_origin->random)  //如果random不为NULL;
            {
                //找到index
                index = node_map_origin[cur_origin->random];
             //2.通过index找出新链表中的地址
                ptr = node_map_new[index];
            }
            //3.构建新链表的random
            cur_new->random = ptr;

            cur_new= cur_new->next;
            cur_origin = cur_origin->next;
        }
        return head_new;
    }
}; 

结果效率并没有提升多少。大概是amp通过key找val也是要遍历的吧
在这里插入图片描述

try3:一个map+一个vector

vector是一个可以实现随机访问的容器,对于第二个通过序号来找地址的情况十分适合。用node_vector_new来替代node_map_new,语法上进行相应调整即可。

修改过程中,我还发现原node_map_new中少了最后一个节点,应该拿出来放在 if 语句外边。C++map找不到对应的key直接是返回最后一个end()指针,是最后一个节点的下一个,没有任何意义。

修改后的代码如下:

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head){return NULL;}

        std::map<Node*,int> node_map_origin;
        std::vector<Node*> node_vector_new;
        int index = 0;

        Node* cur_origin = head;
        Node* cur_new = new Node(cur_origin->val);
        Node* head_new = cur_new;  //记录下新链表的开头

        //第一遍遍历原链表,做三件事:
        while(cur_origin)
        {
            //1.构建原来链表地址:位置的map;
            node_map_origin[cur_origin] = index;

            //2.构建新链表的顺序结构,
            if(cur_origin->next)
            {
                cur_new->next = new Node(cur_origin->next->val); //构建新链表的顺序结构
            }
            else{cur_new->next = NULL;}
            //3.将新链表的地址push进vector里
            node_vector_new.push_back(cur_new);
            
            //4.继续向下遍历原链表,注意新链表也得向下遍历
            cur_origin = cur_origin->next;
            cur_new = cur_new->next;
            //5.位置加一
            index++;
        }
        //第二遍遍历原链表,做三件事:
        cur_origin = head;
        cur_new = head_new;
        while(cur_new)
        {
            //1.找出原链表random指针的index
            Node* ptr = NULL;
            if(cur_origin->random)  //如果random不为NULL;
            {
                //找到index
                index = node_map_origin[cur_origin->random];
             //2.通过index找出新链表中的地址
                ptr = node_vector_new[index];
            }
            //3.构建新链表的random
            cur_new->random = ptr;

            cur_new= cur_new->next;
            cur_origin = cur_origin->next;
        }
        return head_new;
    }
}; 

结果好了很多:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值