python_ACM模式《剑指offer刷题》链表2

题目:

例如

面试tips:

询问有无时间复杂度或空间复杂度的限制。

思路:

本题的本质就是复杂链表的深拷贝

  1. 暴力解法 → 第一次遍历原链表时构建一个复制了next的新链表,第二次遍历原链表,对每个原链表的节点的random从头寻找,同时同步在新链表寻找,即可找到复制链表每个节点的random。

时复O(N^2), 空复O(1)

  1. 用空间换取时间,利用哈希表。在第一次遍历原链表时存储(原链表节点,新链表节点)的映射关系;则在第二次遍历原链表时为新链表根据哈希映射找到其next和random。

时复O(N), 空复O(N)

  1. 拼接+拆分;时复O(N),空复O(1)。

第一步:遍历原链表时对其进行复制,并将原链表(A,B,C,D...)与新链表(A',B',C',D',...)拼接成如下形式。

第二步:遍历上述链表的原节点(curr = curr.next.next),只要curr.random不为空,则curr.next(新链表对应节点)的random即为curr.random.next。说人话就是:上图中第一步构建完后就是上图,可以看到新链表节点(A',B',C',D',...)是没有random指针的(random指针为None), 这时可通过其前一个节点帮助找其复制后的random节点,例如

第三步:将其交错拆分,拆分时用next指针连接即可。

代码实现:

因为是acm模式,因此包括了二维数组转复杂链表,复杂链表转二维数组的过程,因此较长。核心代码模式从class solution开始,但很多大厂面试就是要自己从数组转链表,再从链表转数组进行打印,因此这里记录acm模式。

1. 思路一实现

python 复制代码
class ListNode:
    def __init__(self, val , next = None, random = None):
        self.val = val
        self.next = next
        self.random = random

def arr2List(arr_2):
    # 二维数组构建复杂链表函数
    # 构建时将其存入哈希表
    my_dict = {}
    dummy_head = prev = ListNode(0)
    for i in range(len(arr_2)):
        my_dict[i] = prev.next = ListNode(arr_2[i][0])
        prev = prev.next
    # 至此dummy_head即为构建好的带有next的链表,但是没有random指针
    # 二维数组的第二个数字表示该节点random指针所指向的第几个节点
    prev = dummy_head.next
    for i in range(len(arr_2)):
        if arr_2[i][1] != None:
            prev.random = my_dict[arr_2[i][1]]
        else:
            prev.random = None
        prev = prev.next
    return dummy_head.next

def List2arr(head):
    # 复杂链表转二维数组
    arr_2 = []
    my_dict = {}
    curr = head
    i = 0
    while curr:
        tmp = [curr.val]
        arr_2.append(tmp)
        my_dict[curr] = i
        i += 1
        curr = curr.next
    curr = head
    while curr:
        # 添加random
        arr_2[my_dict[curr]].append(my_dict[curr.random] if curr.random else None)
        curr = curr.next
    return arr_2

class Solution:
    def copyRandomList(self, head):
        # 暴力方法 -- 先构建只含有next的单链表,再构建random的
        curr = head
        new_head = prev = ListNode(0)   # 虚拟指针 使得头节点操作与其他同
        while curr:
            new_curr = ListNode(curr.val)
            prev.next = new_curr
            prev, curr = new_curr, curr.next
        # 至此 复制了链表的第一层(即带next指针的)
        # 接下来 需要给复制链表上的每个节点添加random指针 原链表上从头开始走了多少步则复制链表上也从头开始走了多少步
        curr1 = head
        new_curr1 = new_head.next
        while curr1:
            curr2 = head
            new_curr2 = new_head.next
            # 找到curr1所对应的random,同步找到new_curr1所对应的random
            while curr2 and curr1.random != curr2:
                curr2 = curr2.next
                new_curr2 = new_curr2.next
            # 跳出来 此时即 curr2 == curr1.random,那么new_curr2所指向的位置即为new_curr1的random
            new_curr1.random = new_curr2
            curr1 = curr1.next
            new_curr1 = new_curr1.next
        return new_head.next

if __name__ == '__main__':
    # 复杂链表的构建,二维数组构建复杂链表,(用单链表用一维数组、双链表用二维数组构建的思想)
    arr_2 = [[7, None],[13, 0],[11, 4],[10, 2],[1, 0]]
    a = Solution()
    # 先将二维数组转为链表
    head = arr2List(arr_2)
    new_head = a.copyRandomList(head)
    # 再将链表转为二维数组
    new_arr2 = List2arr(new_head)
    print(new_arr2)

2. 思路二实现

python 复制代码
class ListNode:
    def __init__(self, val , next = None, random = None):
        self.val = val
        self.next = next
        self.random = random

def arr2List(arr_2):
    # 二维数组构建复杂链表函数
    # 构建时将其存入哈希表
    my_dict = {}
    dummy_head = prev = ListNode(0)
    for i in range(len(arr_2)):
        my_dict[i] = prev.next = ListNode(arr_2[i][0])
        prev = prev.next
    # 至此dummy_head即为构建好的带有next的链表,但是没有random指针
    # 二维数组的第二个数字表示该节点random指针所指向的第几个节点
    prev = dummy_head.next
    for i in range(len(arr_2)):
        if arr_2[i][1] != None:
            prev.random = my_dict[arr_2[i][1]]
        else:
            prev.random = None
        prev = prev.next
    return dummy_head.next

def List2arr(head):
    # 复杂链表转二维数组
    arr_2 = []
    my_dict = {}
    curr = head
    i = 0
    while curr:
        tmp = [curr.val]
        arr_2.append(tmp)
        my_dict[curr] = i
        i += 1
        curr = curr.next
    curr = head
    while curr:
        # 添加random
        arr_2[my_dict[curr]].append(my_dict[curr.random] if curr.random else None)
        curr = curr.next
    return arr_2

class Solution:
    def copyRandomList(self, head) :
        # 前一种方法时复为O(n^2),主要是在寻找random指针时用时太久
        # 用空间换时间,在一次构造遍历时就存储好每个节点random指针所对应的复制节点
        # 建立哈希映射
        if not head:
            return None
        my_dict = {}
        curr = head
        while curr:
            my_dict[curr] = ListNode(curr.val)
            curr = curr.next
        # 对复制的链表进行next和random指针赋值
        curr = head
        while curr:
            # 这里不用my_dict[curr].next = my_dict[curr.next]
            # my_dict[curr].random = my_dict[curr.random]
            # 的原因是当curr.random为空时 是没有被存储下来 因此用get 若取不到则为None
            my_dict[curr].next = my_dict.get(curr.next)
            my_dict[curr].random = my_dict.get(curr.random)
            curr = curr.next
        return my_dict[head]

if __name__ == '__main__':
    # 复杂链表的构建,二维数组构建复杂链表,(用单链表用一维数组、双链表用二维数组构建的思想)
    arr_2 = [[7, None],[13, 0],[11, 4],[10, 2],[1, 0]]
    a = Solution()
    # 先将二维数组转为链表
    head = arr2List(arr_2)
    new_head = a.copyRandomList(head)
    # 再将链表转为二维数组
    new_arr2 = List2arr(new_head)
    print(new_arr2)

3. 思路三实现

python 复制代码
class ListNode:
    def __init__(self, val , next = None, random = None):
        self.val = val
        self.next = next
        self.random = random

def arr2List(arr_2):
    # 二维数组构建复杂链表函数
    # 构建时将其存入哈希表
    my_dict = {}
    dummy_head = prev = ListNode(0)
    for i in range(len(arr_2)):
        my_dict[i] = prev.next = ListNode(arr_2[i][0])
        prev = prev.next
    # 至此dummy_head即为构建好的带有next的链表,但是没有random指针
    # 二维数组的第二个数字表示该节点random指针所指向的第几个节点
    prev = dummy_head.next
    for i in range(len(arr_2)):
        if arr_2[i][1] != None:
            prev.random = my_dict[arr_2[i][1]]
        else:
            prev.random = None
        prev = prev.next
    return dummy_head.next

def List2arr(head):
    # 复杂链表转二维数组
    arr_2 = []
    my_dict = {}
    curr = head
    i = 0
    while curr:
        tmp = [curr.val]
        arr_2.append(tmp)
        my_dict[curr] = i
        i += 1
        curr = curr.next
    curr = head
    while curr:
        # 添加random
        arr_2[my_dict[curr]].append(my_dict[curr.random] if curr.random else None)
        curr = curr.next
    return arr_2

class Solution:
    def copyRandomList(self, head):
        # 在不使用额外空间的情况下 时复为O(n).
        if not head:
            # 这里提前判断是为第三步时要赋予两个头节点
            return None
        # 1先将复制链表与原链表串联
        curr = head
        while curr:
            new_curr = ListNode(curr.val)
            new_curr.next = curr.next
            curr.next = new_curr
            curr = new_curr.next
        # 这样就将复制链表与原链表形成一个单链表,奇数节点为单链表(有random),偶数节点为复制链表(random未赋值)
        # 2为复制链表添加random(复制链表的节点特征为random为空)从不为空的random节点提取出random的next给next
        curr = head
        while curr:
            if curr.random:
                # 只要curr.random不为空,则其next的random与其random复制的节点同,即random.next
                curr.next.random = curr.random.next
            curr = curr.next.next   # 这里不curr=curr.next的原因是在执行时curr.next可能会空
        # 至此,就已连接好random,下面进行拆分并链接next
        # 3.现进行拆分并连接,构成next
        curr = head
        new_prev = new_curr = head.next
        while new_curr.next:
            curr.next = new_curr.next
            new_curr.next = new_curr.next.next
            curr, new_curr = curr.next, new_curr.next
        # 还原原链表结构(单独处理其尾节点)
        curr.next = None
        return new_prev

if __name__ == '__main__':
    # 复杂链表的构建,二维数组构建复杂链表,(用单链表用一维数组、双链表用二维数组构建的思想)
    arr_2 = [[7, None],[13, 0],[11, 4],[10, 2],[1, 0]]
    a = Solution()
    # 先将二维数组转为链表
    head = arr2List(arr_2)
    new_head = a.copyRandomList(head)
    # 再将链表转为二维数组
    new_arr2 = List2arr(new_head)
    print(new_arr2)

参考资料:

  1. 《剑指offer》

  2. 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

相关推荐
冠位观测者7 分钟前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
就爱学编程1 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
ALISHENGYA2 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
DARLING Zero two♡3 小时前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode
波音彬要多做4 小时前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
Noah_aa4 小时前
代码随想录算法训练营第五十六天 | 图 | 拓扑排序(BFS)
数据结构
KpLn_HJL5 小时前
leetcode - 2139. Minimum Moves to Reach Target Score
java·数据结构·leetcode
AC使者11 小时前
5820 丰富的周日生活
数据结构·算法
无 证明11 小时前
new 分配空间;引用
数据结构·c++