138. 复制带随机指针的链表

概述

此题同 剑指 Offer 35. 复杂链表的复制

这道题解法非常多

分析

如果没有random指针,那么就是普通的单链表
现在多出来的条件是,有一个random指针,要求我们把random指针也拷贝下来
random指针指向的是结点,不是index,如果是index就好办很多

题解

暴力 使用额外的存储空间

现在关键的地方是我们怎么把random指针指向的结点保存下来
由于这个自定义类是可变的,我们没办法做hash来记录index
一个暴力的方法是,用list来存储链表中每个结点,然后对每个random指针进行校对,得到所有random指针的下标,用下标信息来生成新的链表
大约是O(n^2)的时间复杂度,1000个结点的话应该能暴力通过

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        dh = Node(-1)
        ini_lst = []
        p = head
        while p:
            ini_lst.append(p)
            p = p.next
        ini_lst.append(None)

        # 保存下标信息
        ind = [0 for i in range(len(ini_lst)-1)]
        for cur,p in enumerate(ini_lst):
            if p == None:
                break 
            rn = p.random
            for i in range(len(ini_lst)):
                if ini_lst[i] == rn:
                    ind[cur] = i    # random指向结点的下标
                    break

        new_lst = []       
        for i in range(len(ini_lst)-1):
            new_lst.append(Node(ini_lst[i].val,None,None))
        
        new_lst.append(None)
        for i in range(len(ini_lst)-1):
            new_lst[i].random = new_lst[ind[i]]
        
        new_head = dh
        for i in range(len(ini_lst)-1):
            new_head.next = new_lst[i]
            new_head=new_head.next

        return dh.next

上面用了两个list,两个循环遍历保存random指向的下标,然后第二个list用来生成新的结点
在这里插入图片描述
时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( n ) O(n) O(n)

如果我们用hash的话,会更加简洁

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head:
            return head
        dct = {}
        cur = head
        # 原结点和新结点的映射
        while cur:
            dct[cur] = Node(cur.val)
            cur = cur.next
        
        # 新结点的指针处理
        cur = head
        while cur:
            dct[cur].next = dct.get(cur.next)
            dct[cur].random = dct.get(cur.random)
            cur = cur.next

        return dct[head] 

主要是原结点和新结点的映射,还有第二个循环处理很巧妙

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

转换为图

还是第一次碰到这种写法

按照next指针和random指针可以构造出一个图,head仍然是图的入口
对图的搜索有两个版本,DFS和BFS

DFS

从头结点 head 开始拷贝;
由于一个结点可能被多个指针指到,因此如果该结点已被拷贝,则不需要重复拷贝;
如果还没拷贝该结点,则创建一个新的结点进行拷贝,并将拷贝过的结点保存在哈希表中;
使用递归拷贝所有的 next 结点,再递归拷贝所有的 random 结点。

作者:z1m
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/lian-biao-de-shen-kao-bei-by-z1m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        def dfs(head):
            if not head:
                return head
            if head in visited:
                return visited[head]
            copy = Node(head.val)
            visited[head] = copy
            copy.next = dfs(head.next)
            copy.random = dfs(head.random)
            return copy
        
        visited = {}

        return dfs(head) 

还是要用到hash,不难,就是转换为图遍历这个操作不太好想

BFS

懒得写了,直接copy

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        visited = {}
    
        def bfs(head):
            if not head: return head
            clone = Node(head.val, None, None) # 创建新结点
            queue = collections.deque()
            queue.append(head)
            visited[head] = clone
            while queue:
                tmp = queue.pop()
                if tmp.next and tmp.next not in visited:
                    visited[tmp.next] = Node(tmp.next.val, [], [])
                    queue.append(tmp.next)  
                if tmp.random and tmp.random not in visited:
                    visited[tmp.random] = Node(tmp.random.val, [], [])
                    queue.append(tmp.random)
                visited[tmp].next = visited.get(tmp.next)
                visited[tmp].random = visited.get(tmp.random)
            return clone
        return bfs(head)

作者:z1m
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/lian-biao-de-shen-kao-bei-by-z1m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

copy和拆分

如果在原链表的每个结点后加上一个新的结点,那么random的copy就很容易了
A->A’->B->B’->C->C’->None
我们把pre作为旧结点,cur作为新结点
有以下关系:
cur.random = pre.random.next

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head:
            return head
        cur = head
        # 1. copy
        while cur:
            temp = Node(cur.val)
            temp.next = cur.next
            cur.next = temp
            cur = temp.next
        
        # 2. set random
        pre,cur = head,head.next
        while pre:
            if pre.random == None:
                cur.random = None
            else:
                cur.random = pre.random.next
            pre = cur.next
            if not pre:
                break
            cur = pre.next
        
        # 3. get new link
        cur = head.next 
        while cur.next:
            cur.next = cur.next.next
            cur = cur.next

        return head.next

在这里插入图片描述
上面代码写得不够优雅,原链表会断链,我们修改一下

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head:
            return head
        cur = head
        # 1. copy
        while cur:
            temp = Node(cur.val)
            temp.next = cur.next
            cur.next = temp
            cur = temp.next
        
        # 2. set random
        cur = head
        while cur:
            if cur.random:
                cur.next.random = cur.random.next   # key
            cur = cur.next.next
        
        # 3. get new link
        pre,cur,ans = head,head.next,head.next
        while cur.next:
            pre.next = pre.next.next
            cur.next = cur.next.next
            pre = pre.next
            cur = cur.next
        pre.next = None

        return ans 

现在第二个循环写得更加符合特性,原链表也不会断链,nice
时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( 1 ) O(1) O(1)

总结

一共介绍了3个方法
暴力(用list和用hash)
转换为图
copy和拆分

像这种题目呢,暴力比较好想到,做是能做的,但是更巧妙一点的解法就不好想到了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值