算法修炼之路——【链表】Leetcode 138 复制带随机指针的链表

题目描述

给定一个链表,每个节点包含一个额外增加的随即指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的深拷贝 (这里可以理解为,拷贝前后的数据为完全独立的内存空间,不存在指针共用、数据共享,只是二者对应节点的val数值相等而已)。

我们用一个由n个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]表示:

  • val : 一个表示Node.val的整数。
  • random_index : 随即指针指向的节点索引(范围从0n - 1; 如果不指向任何节点,则为null)。

示例1:

输入: head = [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
输出: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
在这里插入图片描述
图1

示例2:

输入: head = [[1, 1], [2, 1]]
输出: [[1, 1], [2, 1]]
在这里插入图片描述
图2

示例3:

输入: head = []
输出: []
解释: 给定的链表为空(空指针),因此返回null

提示:

  • -1000 <= node.val <= 10000;
  • Node.random为空(null)或指向链表中的节点;
  • 节点数目不超过1000

思路分析

我们需要注意的是:

  • 题干要求为深拷贝
  • next指针为链表规则排列; random为任一排列。

我们可以通过next指针对链表进行遍历,难点是:

  1. random的节点判存;
  2. random如何进行链接;
  3. next如何进行判存以及链接?

对于难点1
我们可以通过一个辅助容器visitedNodeMap,来暂存我们访问过的节点,但是由于是深拷贝,我们首先需要在原链表中判断是否访问过这个节点,我们不妨设其为oldNode之后在新链表中来新建节点newNode同时完善其后置节点newNode.next与随机指针节点newNode.random

这里我们对新旧节点均进行了操作,所以我们需要设置辅助容器为一个二元数据结构,这里通常使用HashMap,由于oldNode为主标识,我们将key设置为oldNode, value设置为 newNode, 故这里的辅助容器初始化为:

Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();

对于难点2
random节点的链接问题,我们需要对访问历史visitedNodeMap先判断oldNode.random是否存在:

  1. 如果存在,直接返回visitedNodeMap.get(oldNode.random);
  2. 如果不存在,新建节点node = new ListNode(oldNode.random.val, _next = null, _random = null),并将这对节点放入访问历史visitedNodeMap.put(oldNode.random, node)

对于难点3
其实这个问题,在难点1, 2中已经有了答案,只是将对象oldNode.random换成了oldNode.next, 这里我们不妨将此过程进行函数封装,功能为获取指定节点的深度拷贝

代码1:获取给定节点的深度拷贝

Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();

public ListNode getClone(ListNode node){
	if(node == null) return null;
	
	if(!visitedNodeMap.contains(node))
		visitedNodeMap.put(node, new ListNode(node.val, 
											_next = null, _random = null));
	
	return visitedNodeMap.get(node);
	
}

解题步骤

  1. 初始化指针,并对首节点进行深拷贝;
  2. 遍历原始链表,完善nextrandom节点信息;
  3. 从访问历史visitedNodeMap中返回结果visitedNodeMap.get(head).

解题代码

public class Leetcode138_copy_list_with_random_pointer {

    static Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();
    
    static class ListNode {

        int val;
        ListNode next;
        ListNode random;

        ListNode(int x) {
            this(x, null, null);
        }

        ListNode(int x, ListNode _next, ListNode _random) {
            val = x;
            next = _next;
            random = _random;
        }
    }

    public static ListNode solution(ListNode head) {
        if (head == null) {
            return null;
        }

        /* Step1:
        Init. pointer
        and
        copy the very first node
         */
        ListNode oldNode = head;

        ListNode newNode = new ListNode(head.val, null, null);
        visitedNodeMap.put(oldNode, newNode);
        /* Step2: go through the head-list
        and
        complement info of 'newNode.random' and 'newNode.next'
         */       
        while (oldNode != null) {
            newNode.random = getCloneNode(oldNode.random);
            newNode.next = getCloneNode(oldNode.next);

            oldNode = oldNode.next;
            newNode = newNode.next;
        }

        
        /* Step3: return new deep copy list
         */
        return visitedNodeMap.get(head);
    }

    public static ListNode getCloneNode(ListNode node) {
        if(node == null) return null;
        
       
            if (!visitedNodeMap.containsKey(node))
                 visitedNodeMap.put(node, new ListNode(node.val, null, null));
            
        
        return visitedNodeMap.get(node);
    }

复杂度分析

时间复杂度:我们对原始链表进行了一次遍历,容易理解时间复杂度为O(N);
空间复杂度:我们这里设置了辅助容器visitedNodeMap,里面维护着2N个链表节点,故空间复杂度为O(N).

GitHub源码

完整可运行文件请访问GitHub

发布了47 篇原创文章 · 获赞 55 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u011106767/article/details/105531632