Leetcode-138 复制带随机指针的链表

题目

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

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

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

你的代码 接受原链表的头节点 head 作为传入参数。

 

示例 1:

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

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

 

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.random 为 null 或指向链表中的节点。

我的思路

由于在新链表中连接random节点时该节点可能还没创建,因此需要先创建完新节点,再使用Map做一个新节点的映射,随后遍历原链表并使用这个映射给新节点的random指针赋值。需要注意选择映射的key值,key值应该是不重复的,所以可以选择原节点地址作key值,新节点地址作value值。刚开始选择的是原节点的val值作key值,所以当原节点有重复的val值时就出错了。而且选择原节点的val值作key值时在遇到null节点时还要额外处理。

错误代码:

// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    /*
		不推荐这样做:先按值构造各个节点,不连接,将节点放入Map中,key为val,value为节点。然后再遍历一次原链表,按random的值检索新节点。更新节点的random值。
		key值不应该为节点值,因为节点值可能是相同的,就导致会有许多相同的key值。而且当节点为null时还不能直接取key值,还要进行额外处理。
		应该将key设为原来的节点,因为每个节点地址是不同的。而且即使节点为null,Java的Map中取一个不存在的key值返回的也是null,不会报错,所以不用额外处理。
    */
    public Node copyRandomList(Node head) {
        Node newRandomList = new Node(0);
        Node tempNode = head;
        List<Integer> randomValList = new ArrayList<>();
        Map<Integer, Node> nodeMap = new HashMap<>();

        while(tempNode != null){
            Node newNode = new Node(tempNode.val);
            nodeMap.put(tempNode.val, newNode);
            tempNode = tempNode.next;
        }

        tempNode = head;
        while(tempNode != null){
            if(tempNode.random != null)
                nodeMap.get(tempNode.val).random = nodeMap.get(tempNode.random.val);
            else
                nodeMap.get(tempNode.val).random = null;

            if(tempNode.next != null)
                nodeMap.get(tempNode.val).next = nodeMap.get(tempNode.next.val);
            else
                nodeMap.get(tempNode.val).next = null;

            System.out.println(nodeMap.get(tempNode.val));
            tempNode = tempNode.next;
        }

        return nodeMap.get(head.val);
    }
}

正确代码:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    /*
    不推荐这样做:先按值构造各个节点,不连接,将节点放入Map中,key为val,value为节点。然后再遍历一次原链表,按random的值检索新节点。更新节点的random值。
    key值不应该为节点值,因为节点值可能是相同的,就导致会有许多相同的key值。而且当节点为null时还不能直接取key值,还要进行额外处理。
    应该将key设为原来的节点,因为每个节点地址是不同的。而且即使节点为null,Java的Map中取一个不存在的key值返回的也是null,不会报错,所以不用额外处理。
    */
    public Node copyRandomList(Node head) {
        Map<Node, Node> nodeMap = new HashMap<>();
        Node tempNode = head;

        while(tempNode != null){
            //更简洁地给map赋值
            nodeMap.put(tempNode, new Node(tempNode.val));
            tempNode = tempNode.next;
        }

        tempNode = head;
        while(tempNode != null){
            nodeMap.get(tempNode).next = nodeMap.get(tempNode.next);
            nodeMap.get(tempNode).random = nodeMap.get(tempNode.random);
            tempNode = tempNode.next;
        }

        return nodeMap.get(head);
    }
}

节点拆分法

为原链表的每个节点都新建一个新的相同节点连接在其后,然后第二次遍历时就可以把随机指针指向相应节点的下一个新创建节点,第三次遍历把新链表重新连接,并且要把原链表还原,否则这算修改链表了,无法通过测试用例。新链表的random和next指针更新的过程不能再一个循环进行,否则下一次迭代再更新random指针就会出现混乱。总的来说,这个方法还是挺容易出错的。
错误代码:
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    //为原链表的每个节点都新建一个新的相同节点连接在其后,然后第二次遍历时就可以把随机指针指向相应节点的下一个新创建节点
    public Node copyRandomList(Node head) {
        Node tempNode = head;
        Node currNode = null;
        Node newRandomList = new Node(0);

        while(tempNode != null){
            Node newNode = new Node(tempNode.val);
            currNode = tempNode.next;
            tempNode.next = newNode;
            newNode.next = currNode;
            tempNode = currNode;
        }
        
        //不能同时更改ramdom指针和next指针,因为更改next指针后,下一轮指向的random.next指针就会混乱
        tempNode = head;
        newRandomList.next = head.next;
        while(tempNode != null){
            currNode = tempNode.next;
            if(tempNode.random != null){
                currNode.random = tempNode.random.next;
                System.out.println(tempNode.random);
                System.out.println(tempNode.random.next);}
            else
                currNode.random = null;
            tempNode.next = tempNode.next.next;
            if(currNode.next != null)
                currNode.next = currNode.next.next;
            
            System.out.println(tempNode.val);
            tempNode = tempNode.next;
        }

        return newRandomList.next;
    }
}

正确代码:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    //为原链表的每个节点都新建一个新的相同节点连接在其后,然后第二次遍历时就可以把随机指针指向相应节点的下一个新创建节点。
    public Node copyRandomList(Node head) {
        Node tempNode = head;
        Node currNode = null;
        Node newRandomList = new Node(0);

        //要注意考虑空链表的情况
        if(head == null){
            return null;
        }

        while(tempNode != null){
            Node newNode = new Node(tempNode.val);
            currNode = tempNode.next;
            tempNode.next = newNode;
            newNode.next = currNode;
            tempNode = currNode;
        }
        
        //不能同时更改ramdom指针和next指针,因为更改next指针后,下一轮指向的random.next指针就会混乱
        tempNode = head;
        while(tempNode != null){
            currNode = tempNode.next;
            currNode.random = (tempNode.random == null) ? null : tempNode.random.next;
            //要注意跳过复制节点
            tempNode = tempNode.next.next;
        }

        //要注意将tempNode置为head节点
        tempNode = head;
        newRandomList.next = head.next;
        while(tempNode != null){
            currNode = tempNode.next;
            //要注意先更改tempNode.next再更改currNode.next
            tempNode.next = tempNode.next.next;
            currNode.next = (currNode.next == null) ? null : currNode.next.next;
            //要注意更新tempNode
            tempNode = tempNode.next;
        }
        return newRandomList.next;
    }
}

递归法

先递归地创建每个新节点,借助哈希表建立每个节点和新节点的映射,创建好节点后就可以给当前节点的next和random赋值了。如果新节点在哈希表中,说明新节点已经创建好了,直接返回即可。要注意创建好节点后就做立即做哈希表映射,next和random的赋值过程在回溯过程会进行。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    //在需要指向新的random节点时,递归进入深层创建新节点。需要借助哈希表记录每一个节点对应新节点的创建情况,这样可以避免重复创建新节点。
    Map<Node, Node> nodeMap = new HashMap<>();
    public Node copyRandomList(Node head) {
        if(head == null){
            return null;
        }

        if(!nodeMap.containsKey(head)){
            //如果还未创建新节点,则创建并递归地给next和random赋值
            Node newNode = new Node(head.val);
            //要先将新节点和当前节点做映射
            nodeMap.put(head, newNode);
            //以下过程都是先创建next和random节点,再更新新节点的next和random的值
            newNode.next = copyRandomList(head.next);
            newNode.random = copyRandomList(head.random);
        }

        //当head在哈希表中,说明新节点已经创建完毕,直接返回
        return nodeMap.get(head);
    }
}

学到和回忆了:

  • Java的Map的使用
    //构造一个新Map
    Map newMap = new HashMap();
    
    //往Map中放入键值对
    newMap.put(key, value);
    
    //根据key取value
    newMap.get(key);
    
    //移除某个key的映射
    newMap.remove(key);
    
    //获取所有的key
    newMap.keySet();
    
    //获取所有的value
    newMap.values();
    
    //查看Map中是否有对应的key
    newMap.containsKey(key);
    
    //查看Map中是否有对应的value
    newMap.containsValue(value);
    
    //查看Map中的映射对个数
    newMap.size();
    
    //查看Map中是否有元素
    newMap.isEmpty();
    
    //清楚所有的映射对
    newMap.clear();
  • Java中的Map获取不存在的key值会返回null而不会报错
  • Java中的Map是不按顺序存放数据的,如果要按顺序存放数据,则需要使用LinkedHashMap

参考:

复制带随机指针的链表 - 复制带随机指针的链表 - 力扣(LeetCode) (leetcode-cn.com)

posted @ 2022-04-08 20:29  心空之上  阅读(26)  评论(0)    收藏  举报