剑指 Offer JZ35 复杂链表的复制

注:本文的代码实现使用的是 JS(JavaScript),为前端中想使用JS练习算法和数据结构的小伙伴提供解题思路。

描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
在这里插入图片描述


示例:

输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}

解析: 我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。
以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5
后半部分,3,5,#,2,#分别的表示为
1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null
如下图:

在这里插入图片描述


解题思路

直接法

首先按照正常链表的思路复制出来一个新的链表,其中pHead是题中给出需要复制的链表的头节点。RandomListNode是创建节点的类,其结构如下:

function RandomListNode(x){
	// label 相当于普通链表的 val
    this.label = x;
    // 当前节点的下一个节点
    this.next = null;
    // 当前节点所指向的随机节点
    this.random = null;
}

复制链表的代码如下,新链表的头节点为newHead

	// 复制头节点指针,用于遍历链表
	// 如果直接用 pHead 进行遍历,后续再想找到原始链表的头节点就找不到了
	let p = pHead
	// 手动创建一个头节点,也就是新的链表的头节点
    let newHead = new RandomListNode(p.label)
    // 新的链表的节点指针,用于遍历
    let p_newHead = newHead
    while(p.next){
        p_newHead.next = new RandomListNode(p.next.label)
        p = p.next
        p_newHead = p_newHead.next
    }

至此,我们的工作可以看作是完成了一半,剩下的就是把新链表中每个节点的random指针指向对应的节点即可。那么我们该如何去做呢?

解决方案肯定不止一种,本人想到了一种思路:因为链表是有序的,因此,我们可以根据索引,去寻找这个关系。

以题中给的示例说明,原始链表中,D的random指针指向的是B,而B在整个链表中的索引是1(索引从0开始),因此在我们创建的新的链表中,让D’(新链表中根据原始链表中D创建的节点)指向新创建节点的第1个节点即可。

所以,创建了一个新的函数find去查找节点noderandom所指向节点在其所在链表中的索引,结构如下:

function find(node, pHead){
	// node 节点的 random 指针指向空, 则索引为 null
    if(!node.random) return null
    // 记录索引的变量
    let index = 0
    while(pHead){
    	// 如果当前节点和node 节点的 random 指向的是同一个节点,则返回 index
        if(pHead === node.random) return index
        pHead = pHead.next
        index ++
    }
    // 此处无需进行其他 return 因为一定能找到索引的
}

最后本人创建了两个变量:

// 用于存放索引的数组,初始化的时候,将头节点的 random 指向节点的index放入
let list = [find(pHead, pHead)]
// 用于存放新创建链表的每个节点的指针,方便给新节点的 random 赋值
let node_pointer = [p_newHead]

完整的代码如下:

function find(node, pHead){
    if(!node.random) return null
    let index = 0
    while(pHead){
        if(pHead === node.random) return index
        pHead = pHead.next
        index ++
    }
}
function Clone(pHead)
{
	// 头节点为 null ,则直接返回即可
    if(!pHead) return pHead
    let p = pHead
    let newHead = new RandomListNode(p.label)
    let p_newHead = newHead
    let list = [find(pHead, pHead)]
    let node_pointer = [p_newHead]
    while(p.next){
        p_newHead.next = new RandomListNode(p.next.label)
        // 在创新新链表的时候,顺便按顺序把原始链表每个节点的 random 所指向节点的索引记录
        list.push(find(p.next, pHead))
        // 也顺便存放新创建链表的每个节点的指针
        node_pointer.push(p_newHead.next)
        p = p.next
        p_newHead = p_newHead.next
    }
    p_newHead = newHead
    let count = 0
    while(p_newHead){
    	// list[count] 是当前节点random 指向节点的索引
    	// 然后再从 node_pointer 找到对应的指针即可
        p_newHead.random = node_pointer[list[count]]
        // 为了清晰,把这句话单独拿出来了,其实可以合并到上一句代码中
        count++
        p_newHead = p_newHead.next
    }
    return newHead
}

这个方法可能看着乱,但这是最直观,最容易理解的,时间复杂度是 O ( n 2 ) O(n^2) O(n2)的。其实,你可以直接return pHead的,也能通过,就看面试官办不办你了,哈哈哈哈。

原地复制法

  1. 把原始链表中的每个节点复制,并按下图的方式串接起来,只需要给新创建的节点指定label和next即可
    在这里插入图片描述
    代码如下:
    let p = pHead
    while(p){
        const temp = p.next
        // 创建节点并指定label
        p.next = new RandomListNode(p.label)
        // 为新创建的节点指定next
        p.next.next = temp
        p =  temp
    }
  1. 把复制的结点的random指针指向被复制结点的random指针的下一个结点
    在这里插入图片描述
    p = pHead
    while(p){
    	// 保证当前节点的 random 非空,若为空,就不用管了
        if(p.random)
            p.next.random = p.random.next
        p = p.next.next
    }
3. 拆分成两个链表,具体拆分方式可以看下面代码,跟着代码走一两个循环就能明白了

在这里插入图片描述

 	p = pHead
 	// 新的链表的头节点
    let newHead = p.next
    // 用于遍历新的链表的指针
    let p_newHead = newHead
    // 到倒数第二个节点结束,本题中是 E ,否则在 p = p.next 的时候会报错
    while(p.next.next){
        p.next = p_newHead.next
        p = p.next
        p_newHead.next = p.next
        p_newHead = p.next
    }
    // 如果这句话不加上,虽然输出的 newHead 没问题,但是原始链表最后一个节点的复制节点(E')也会挂在E的后面
    p.next = null

完整代码如下:

function Clone(pHead)
{
    if(!pHead) return pHead
    let p = pHead
    while(p){
        const temp = p.next
        p.next = new RandomListNode(p.label)
        p.next.next = temp
        p =  temp
    }
    p = pHead
    while(p){
        if(p.random)
            p.next.random = p.random.next
        p = p.next.next
    }
    p = pHead
    let newHead = p.next
    let p_newHead = newHead
    while(p.next.next){
        p.next = p_newHead.next
        p = p.next
        p_newHead.next = p.next
        p_newHead = p.next
    }
    p.next = null
    return newHead
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值