剑指offer系列-面试题35-复杂链表的复制(python)

1. 题目

请实现函数clone_complex_list()复制一个复杂链表。在复杂链表中,每个节点除了有一个m_pNext指针指向下一个节点,还有一个m_sibling指针指向链表中的任意节点或者nullptr。节点的c++定义如下:

struct ComplexListNode
{
	int m_nValue;
	ComplexListNode* m_pNext;
	ComplexListNode* m_pSibling;
}

python 版

class ComplexListNode(object):
	def __init__(self, value, next_node=None, sibling_node=None):
		self.value = value
		self.next = next_node
		self.sibling = sibling_node

Alt


2. 解题思路

如何复制一个复杂链表,首先我们得知道复制一个简单链表的步骤是什么呢?

  1. 复制节点
  2. 通过指针将这些节点连接起来

那么复制复杂链表呢?

  1. 复制节点
  2. 通过指针将这些节点连接起来
  3. 复制sibling指针

2.1 思路1

  1. 遍历链表,复制链表的所有节点,时间复杂度O(n)
  2. 通过next指针连接这些节点,时间复杂度O(n)
  3. 遍历原始链表找到每个节点的sibling指针指向的节点,遍历新链表找到新链表中对应节点的sibling指针指向,时间复杂度O(n^2)

2.2 思路2

但是思路1的时间复杂度太高了,那么有没有什么方法能够降低时间复杂度呢?方法当然是有的,思路1的时间复杂度高就高在第3步,所以我们只要能够降低第3步的时间复杂度就可以了。思路1的第3步是两层循环,因为要先从原始链表中找到sibling的节点,然后再在复制链表中找到对应的sibling节点,这两步都是通过遍历实现。

考虑使用额外的空间来换取更低的时间复杂度
也就是説,只要我们找到了原始链表中sibling节点,也就能直接找到复制链表的sibling节点,而无需再遍历链表。那么就需要将原始链表中的节点和复制链表中的节点映射到一个字典中。字典根据key取值的时间复杂度是O(1),这样我们就能在O(n)时间复杂度内实现第3步了。

2.3 思路3

那么问题又来了,能不能不使用额外的字典呢?当然可以了,把复制的节点依次放在原始节点之后,构成一个新的长链表,奇数都是原始链表,偶数都是复制链表。

那具体应该怎么做呢?
其实这个跟思路2是有些类似的。也是将原始节点和复制节点关联了起来,只不过这次不是通过映射来实现的,而是原始节点的next指针指向的下一个节点就是其复制节点。既然有这样的规则,那么同样的也就能在O(1)找到对应的复制节点的sibling节点了。bingo

3. 代码实现

3.1 思路1(O(n^2))


def clone_node(head):
	"""
	第1步,复制链表中的节点和next指针指向,时间复杂度为O(n)
	:param head: 复杂链表的头节点
	:return c_head: 复制链表的头节点
	"""
	if not head:
		return
	# 头节点存在
	cur = head # 初始化当前节点
	c_head = ComplexListNode(head.value)
	c_cur = c_head
	# 当前节点存在的话
	while cur:
		# 复制节点
		if cur.next:
			c_next = ComplexListNode(cur.next.value)
			c_cur.next = c_next
			c_cur = c_next
			cur = cur.next
		else:
			break
	return c_head

def connected_sibling_nodes(head, c_head):    
    """
    第2步,复制链表中的sibling指针指向,时间复杂度为O(n^2)
    :param head: 复杂链表的头节点
    :param c_head: 复制链表的头节点
    """
    
    if not head or not c_head:
        return

    cur = head
    c_cur = c_head
    
    # print("what's up")
    # print('原始链表',head.value)
    # print('复制链表',c_head.value)

    while cur:
        # print('此时,原始节点是{},复制节点是{}'.format(cur.value, c_cur.value))
        # 注意:每次查找sibling时,c_temp必须从链表头开始,因为当前节点的sibling不一定在此节点的后面哦!!
        c_temp = c_head
        # print('此时c_temp是{}'.format(c_temp.value))
        sibling_node = cur.sibling
        # 这个指针不指向None
        if sibling_node:
            while c_temp:
                if c_temp.value == sibling_node.value:
                    c_cur.sibling = c_temp
                    break
                c_temp = c_temp.next
        c_cur = c_cur.next 
        cur = cur.next
   
   return c_head

def clone(head):
    """
    主函数
    """
    c_head = clone_node(head)
    return connected_sibling_nodes(head, c_head)


if __name__ == '__main__':
    node1 = ComplexListNode(1)
    node2 = ComplexListNode(2)
    node3 = ComplexListNode(3)
    node4 = ComplexListNode(4)
    node5 = ComplexListNode(5)
    
    node1.next = node2
    node2.next = node3
    node3.next = node4
    node4.next = node5

    node1.sibling = node3
    node3.sibling = node2
    node4.sibling = node1

    #res = clone_node(node1)
    res = clone(node1)

    node = res
    while node:
        if node.sibling:
            print("节点的值{}, s节点为{}".format(node.value, node.sibling.value))
        else:
            print("节点的值{}, s节点为None".format(node.value))
        node = node.next
	

3.2 思路2

class ComplexListNode(object):
    def __init__(self, value, next_node=None, sibling_node=None):
        self.value = value
        self.next = next_node
        self.sibling = sibling_node

def clone_node(head):
    """
    第1步,复制链表中的节点和next指针指向,时间复杂度为O(n)
    :param head: 复杂链表的头节点
    :return c_head: 复制链表的头节点
    """
    if not head:
        return
    mapping = {} # 映射原始节点和复制节点
    # 头节点存在
    cur = head # 初始化当前节点
    c_head = ComplexListNode(head.value)
    c_cur = c_head
    mapping[head] = c_head
    # 当前节点存在的话
    while cur:
        # 复制节点
        if cur.next:
            mapping[cur] = c_cur
            c_next = ComplexListNode(cur.next.value)
            c_cur.next = c_next
            c_cur = c_next
            cur = cur.next
        else:
            break
    return c_head, mapping

def connected_sibling_nodes(head, c_head, mapping):    
    """
    第2步,复制链表中的sibling指针指向,使用一个字典,映射原始节点和复制节点,时间复杂度为O(n)
    :param head: 复杂链表的头节点
    :param c_head: 复制链表的头节点
    """
    
    if not head or not c_head:
        return
    #mapping = {}
    cur = head
    c_cur = c_head

    while cur:
        #mapping['cur'] = c_cur
        if cur.sibling:
            # 从字典中直接找到,原始链表中节点的sibling节点对应的那个sibling节点
            c_cur.sibling = mapping.get(cur.sibling)
        cur = cur.next
        c_cur = c_cur.next
           
    return c_head

def clone(head):
    """
    主函数
    """
    c_head, mapping = clone_node(head)
    return connected_sibling_nodes(head, c_head, mapping)


if __name__ == '__main__':
    node1 = ComplexListNode(1)
    node2 = ComplexListNode(2)
    node3 = ComplexListNode(3)
    node4 = ComplexListNode(4)
    node5 = ComplexListNode(5)
    
    node1.next = node2
    node2.next = node3
    node3.next = node4
    node4.next = node5

    node1.sibling = node3
    node3.sibling = node2
    node4.sibling = node1

    #res = clone_node(node1)
    res = clone(node1)

    node = res
    while node:
        if node.sibling:
            print("节点的值{}, s节点为{}".format(node.value, node.sibling.value))
        else:
            print("节点的值{}, s节点为None".format(node.value))
        node = node.next

3.3 思路3

class ComplexListNode(object):
    def __init__(self, value, next_node=None, sibling_node=None):
        self.value = value
        self.next = next_node
        self.sibling = sibling_node

def clone_node(head):
    """
    第1步,将复制的节点放到其对应的复杂链表中的节点的后面,构成一个新的长的链表,
    时间复杂度O(n)
    :param head: 复杂链表的头节点
    """
    # 头节点存在
    cur = head # 初始化当前节点
        
    # 当前节点存在的话
    while cur:
        # 1.首先复制节点
        c_cur = ComplexListNode(cur.value)
        # 2.调整next指针的指向
        # 保存原始节点的next, 原始节点的next指向该节点的复制节点,复制节点的next指向原始节点的next
        temp = cur.next
        cur.next = c_cur
        c_cur.next = temp
        # cur指向下一个原始节点
        cur = c_cur.next
        
    return 

def connected_sibling_nodes(head):    
    """
    第2步,复制链表中的sibling指针指向,时间复杂度为O(n)
    :param head: 复杂链表的头节点
    """
    cur = head
       
    while cur:
        # 获得每一个原始节点的复制节点
        c_cur = cur.next
        # 若原始节点有sibling那么,原始节点的sibling节点的next节点就是复制节点的sibling
        if cur.sibling:
            c_cur.sibling = cur.sibling.next
        # cur指向下一个原始节点
        cur = c_cur.next
                        
    return 

def reconnect_nodes(head):
    """
    将这个长的链表按奇偶分割成两个链表
    :param head: 链表的头节点
    """
    cur = head # 原始链表中的当前节点
    c_head = None # 复制链表的头节点
    c_cur = None # 复制链表中的当前节点
    # 初始化,获得复制链表的头节点,c_cur指向第一个复制节点,cur指向
    if cur:
        c_head = c_cur = cur.next
        cur.next = c_head.next
        cur = cur.next

    while cur:
        c_cur.next = cur.next
        c_cur = c_cur.next
        cur.next = c_cur.next
        cur = cur.next

    return c_head

def clone(head):
    """
    主函数
    """
    clone_node(head)
    connected_sibling_nodes(head)
    return reconnect_nodes(head)


if __name__ == '__main__':
    node1 = ComplexListNode(1)
    node2 = ComplexListNode(2)
    node3 = ComplexListNode(3)
    node4 = ComplexListNode(4)
    node5 = ComplexListNode(5)
    
    node1.next = node2
    node2.next = node3
    node3.next = node4
    node4.next = node5

    node1.sibling = node3
    node3.sibling = node2
    node4.sibling = node1

    #res = clone_node(node1)
    res = clone(node1)

    node = res
    while node:
        if node.sibling:
            print("节点的值{}, s节点为{}".format(node.value, node.sibling.value))
        else:
            print("节点的值{}, s节点为None".format(node.value))
        node = node.next

4. 总结

找出限制代码效率的部分,优化这部分即可提升代码的效率。映射和指针,只要能在O(1)时间找到对应的节点,就降低了这一步的时间复杂度。

5. 参考文献

[1] 剑指offer丛书

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值