数据结构与算法之深入解析复制带随机指针的链表的算法实现

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之深入解析复制带随机指针的链表的算法实现相关的知识,希望对你有一定的参考价值。

一、题目

① 题目描述
  • 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
  • 构造这个链表的深拷贝,深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
  • 例如,如果原链表中有 X 和 Y 两个节点,其中 X.random -> Y ,那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random -> y 。返回复制链表的头节点。
  • 用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
    • val:一个表示 Node.val 的整数;
    • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
  • 你的代码只接受原链表的头节点 head 作为传入参数。
② 示例
  • 示例一:

	输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
	输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
  • 示例二:

	输入:head = [[1,1],[2,1]]
	输出:[[1,1],[2,1]]
  • 示例三:

	输入:head = [[3,null],[3,0],[3,null]]
	输出:[[3,null],[3,0],[3,null]]
  • 示例四:
	输入:head = []
	输出:[]
	解释:给定的链表为空(空指针),因此返回 null。

二、思路分析

① 解法一
  • 本题难点:复制随机指针:
    • 节点 1 的随机指针指向节点 3;
    • 节点 3 的随机指针指向节点 2;
    • 节点 2 的随机指针指向空;

  • 根据遍历到的原节点创建对应的新节点,每个新创建的节点是在原节点后面,比如下图中原节点 1 不再指向原原节点 2,而是指向新节点 1:

  • 用来设置新链表的随机指针,如下图所示,可以观察到这么一个规律:
    • 原节点 1 的随机指针指向原节点 3,新节点 1 的随机指针指向的是原节点 3 的 next;
    • 原节点 3 的随机指针指向原节点 2,新节点 3 的随机指针指向的是原节点 2 的 next;
    • 也就是,原节点 i 的随机指针(如果有的话),指向的是原节点 j,那么新节点 i 的随机指针,指向的是原节点 j 的 next。

  • 将两个链表分离开,返回新链表:

  • Java 算法示例:
	class Solution {
	    public Node copyRandomList(Node head) {
	        if(head==null) {
	            return null;
	        }
	        Node p = head;
	        // 第一步,在每个原节点后面创建一个新节点
	        // 1->1'->2->2'->3->3'
	        while(p!=null) {
	            Node newNode = new Node(p.val);
	            newNode.next = p.next;
	            p.next = newNode;
	            p = newNode.next;
	        }
	        p = head;
	        // 第二步,设置新节点的随机节点
	        while(p!=null) {
	            if(p.random!=null) {
	                p.next.random = p.random.next;
	            }
	            p = p.next.next;
	        }
	        Node dummy = new Node(-1);
	        p = head;
	        Node cur = dummy;
	        // 第三步,将两个链表分离
	        while(p!=null) {
	            cur.next = p.next;
	            cur = cur.next;
	            p.next = cur.next;
	            p = p.next;
	        }
	        return dummy.next;
	    }
	}	
② 解法二
  • 用哈希表来解决这个问题,首先创建一个哈希表,再遍历原链表,遍历的同时再不断创建新节点,再将原节点作为 key,新节点作为 value 放入哈希表中。

  • 再遍历原链表,这次要将新链表的 next 和 random 指针给设置上:

  • 从上图中可以发现,原节点和新节点是一一对应的关系,所以:
    • map.get(原节点),得到的就是对应的新节点;
    • map.get(原节点 .next),得到的就是对应的新节点 .next;
    • map.get(原节点 .random),得到的就是对应的新节点 .random;
  • 所以,只需要再次遍历原链表,然后设置:
    • 新节点 .next -> map.get(原节点 .next);
    • 新节点 .random -> map.get(原节点 .random);
    • 新链表的 next 和 random 都被串联起来;
  • 最后 map.get(head),也就是对应的新链表的头节点,就可以解决此问题。
  • Java 算法示例:
	class Solution {
	    public Node copyRandomList(Node head) {
	        if(head==null) {
	            return null;
	        }
	        // 创建一个哈希表,key是原节点,value是新节点
	        Map<Node,Node> map = new HashMap<Node,Node>();
	        Node p = head;
	        // 将原节点和新节点放入哈希表中
	        while(p!=null) {
	            Node newNode = new Node(p.val);
	            map.put(p,newNode);
	            p = p.next;
	        }
	        p = head;
	        // 遍历原链表,设置新节点的next和random
	        while(p!=null) {
	            Node newNode = map.get(p);
	            // p是原节点,map.get(p)是对应的新节点,p.next是原节点的下一个
	            // map.get(p.next)是原节点下一个对应的新节点
	            if(p.next!=null) {
	                newNode.next = map.get(p.next);
	            }
	            // p.random是原节点随机指向
	            // map.get(p.random)是原节点随机指向  对应的新节点 
	            if(p.random!=null) {
	                newNode.random = map.get(p.random);
	            }
	            p = p.next; 
	        }
	        // 返回头结点,即原节点对应的value(新节点)
	        return map.get(head);
	    }
	}

以上是关于数据结构与算法之深入解析复制带随机指针的链表的算法实现的主要内容,如果未能解决你的问题,请参考以下文章

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

算法如果链表里有随机指针怎么拷贝?-复制带有随机指针的链表-力扣经典题目讲解带图保姆级别教程

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

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

[M链表] lc138. 复制带随机指针的链表(模拟+哈希表)

算法总结之 复制含有随机指针节点的链表