|
| 1 | +# 题目描述(中等难度) |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +给一个链表,返回复制后的链表。链表节点相对于普通的多了一个 `random` 指针,会随机指向链表内的任意节点或者指向 `null`。 |
| 6 | + |
| 7 | +# 思路分析 |
| 8 | + |
| 9 | +这道题其实和 [133 题](https://leetcode.wang/leetcode-133-Clone-Graph.html) 复制一个图很类似,这里的话就是要解决的问题就是,当更新当前节点的 `random` 指针的时候,如果 `random` 指向的是很后边的节点,但此时后边的节点还没有生成,那么我们该如何处理。 |
| 10 | + |
| 11 | +和 [133 题](https://leetcode.wang/leetcode-133-Clone-Graph.html) 一样,我们可以利用 `HashMap` 将节点提前生成并且保存起来,第二次遍历到他的时候直接从 `HashMap` 里边拿即可。 |
| 12 | + |
| 13 | +这里的话就有两种思路,一种需要遍历两边链表,一种只需要遍历一遍。 |
| 14 | + |
| 15 | +# 解法一 |
| 16 | + |
| 17 | +首先利用 `HashMap` 来一个不用思考的代码。 |
| 18 | + |
| 19 | +遍历第一遍链表,我们不考虑链表之间的相互关系,仅仅生成所有节点,然后把它存到 `HashMap` 中,`val` 作为 `key`,`Node` 作为 `value`。 |
| 20 | + |
| 21 | +遍历第二遍链表,将之前生成的节点取出来,更新它们的 `next` 和 `random` 指针。 |
| 22 | + |
| 23 | +```java |
| 24 | +public Node copyRandomList(Node head) { |
| 25 | + if (head == null) { |
| 26 | + return null; |
| 27 | + } |
| 28 | + HashMap<Integer, Node> map = new HashMap<>(); |
| 29 | + Node h = head; |
| 30 | + //生成所有节点 |
| 31 | + while (h != null) { |
| 32 | + Node t = new Node(); |
| 33 | + t.val = h.val; |
| 34 | + map.put(t.val, t); |
| 35 | + h = h.next; |
| 36 | + } |
| 37 | + h = head; |
| 38 | + //更新 next 和 random |
| 39 | + while (h != null) { |
| 40 | + if (h.next != null) { |
| 41 | + map.get(h.val).next = map.get(h.next.val); |
| 42 | + } |
| 43 | + if (h.random != null) { |
| 44 | + map.get(h.val).random = map.get(h.random.val); |
| 45 | + } |
| 46 | + h = h.next; |
| 47 | + } |
| 48 | + return map.get(head.val); |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +# 解法二 |
| 53 | + |
| 54 | +解法一虽然简单易懂,但还是有可以优化的地方的。我们可以只遍历一次链表。 |
| 55 | + |
| 56 | +核心思想就是延迟更新它的 `next`。 |
| 57 | + |
| 58 | +```java |
| 59 | +1 -> 2 -> 3 |
| 60 | + |
| 61 | +用 cur 指向已经生成的节点的末尾 |
| 62 | +1 -> 2 |
| 63 | + ^ |
| 64 | + c |
| 65 | + |
| 66 | +然后将 3 构造完成 |
| 67 | + |
| 68 | +最后将 2 的 next 指向 3 |
| 69 | +1 -> 2 -> 3 |
| 70 | + ^ |
| 71 | + c |
| 72 | + |
| 73 | +期间已经生成的节点存到 HashMap 中,第二次遇到的时候直接从 HashMap 中拿 |
| 74 | +``` |
| 75 | + |
| 76 | +看下代码理解一下含义吧 |
| 77 | + |
| 78 | +```java |
| 79 | +public Node copyRandomList(Node head) { |
| 80 | + if (head == null) { |
| 81 | + return null; |
| 82 | + } |
| 83 | + HashMap<Integer, Node> map = new HashMap<>(); |
| 84 | + Node h = head; |
| 85 | + Node cur = new Node(); //空结点,dummy 节点,为了方便头结点计算 |
| 86 | + while (h != null) { |
| 87 | + //判断当前节点是否已经产生过 |
| 88 | + if (!map.containsKey(h.val)) { |
| 89 | + Node t = new Node(); |
| 90 | + t.val = h.val; |
| 91 | + map.put(t.val, t); |
| 92 | + } |
| 93 | + //得到当前节点去更新它的 random 指针 |
| 94 | + Node next = map.get(h.val); |
| 95 | + if (h.random != null) { |
| 96 | + //判断当前节点是否已经产生过 |
| 97 | + if (!map.containsKey(h.random.val)) { |
| 98 | + next.random = new Node(); |
| 99 | + next.random.val = h.random.val; |
| 100 | + map.put(next.random.val, next.random); |
| 101 | + } else { |
| 102 | + next.random = map.get(h.random.val); |
| 103 | + } |
| 104 | + |
| 105 | + } |
| 106 | + //将当前生成的节点接到 cur 的后边 |
| 107 | + cur.next = next; |
| 108 | + cur = cur.next; |
| 109 | + h = h.next; |
| 110 | + } |
| 111 | + return map.get(head.val); |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +# 解法三 |
| 116 | + |
| 117 | +上边的两种解法都用到了 `HashMap` ,所以额外需要 `O(n)` 的空间复杂度。现在考虑不需要额外空间的方法。 |
| 118 | + |
| 119 | +主要参考了[这里](https://leetcode.com/problems/copy-list-with-random-pointer/discuss/43491/A-solution-with-constant-space-complexity-O(1)-and-linear-time-complexity-O(N))。主要解决的问题就是我们生成节点以后,当更新它的 `random` 的时候,怎么找到之前生成的节点,前两种解法用了 `HashMap` 全部存起来,这里的话可以利用原来的链表的指针域。 |
| 120 | + |
| 121 | + 主要需要三步。 |
| 122 | + |
| 123 | +1. 生成所有的节点,并且分别插入到原有节点的后边 |
| 124 | +2. 更新插入节点的 `random` |
| 125 | +3. 将新旧节点分离开来 |
| 126 | + |
| 127 | +一图胜千言,大家看一下下边的图吧。 |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +代码对应如下。 |
| 132 | + |
| 133 | +```java |
| 134 | +public Node copyRandomList(Node head) { |
| 135 | + if (head == null) { |
| 136 | + return null; |
| 137 | + } |
| 138 | + Node l1 = head; |
| 139 | + Node l2 = null; |
| 140 | + //生成所有的节点,并且分别插入到原有节点的后边 |
| 141 | + while (l1 != null) { |
| 142 | + l2 = new Node(); |
| 143 | + l2.val = l1.val; |
| 144 | + l2.next = l1.next; |
| 145 | + l1.next = l2; |
| 146 | + l1 = l1.next.next; |
| 147 | + } |
| 148 | + //更新插入节点的 random |
| 149 | + l1 = head; |
| 150 | + while (l1 != null) { |
| 151 | + if (l1.random != null) { |
| 152 | + l1.next.random = l1.random.next; |
| 153 | + } |
| 154 | + l1 = l1.next.next; |
| 155 | + } |
| 156 | + |
| 157 | + l1 = head; |
| 158 | + Node l2_head = l1.next; |
| 159 | + //将新旧节点分离开来 |
| 160 | + while (l1 != null) { |
| 161 | + l2 = l1.next; |
| 162 | + l1.next = l2.next; |
| 163 | + if (l2.next != null) { |
| 164 | + l2.next = l2.next.next; |
| 165 | + } |
| 166 | + l1 = l1.next; |
| 167 | + } |
| 168 | + return l2_head; |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +# 解法四 |
| 173 | + |
| 174 | +不利用额外的空间复杂度还有一种思路,参考 [这里](https://leetcode.com/problems/copy-list-with-random-pointer/discuss/43497/2-clean-C%2B%2B-algorithms-without-using-extra-arrayhash-table.-Algorithms-are-explained-step-by-step.)。 |
| 175 | + |
| 176 | +解法三利用原链表的 `next` 域把新生成的节点保存了起来。类似的,我们还可以利用原链表的 `random` 域把新生成的节点保存起来。 |
| 177 | + |
| 178 | +主要还是三个步骤。 |
| 179 | + |
| 180 | +1. 生成所有的节点,将它们保存到原链表的 `random` 域,同时利用新生成的节点的 `next` 域保存原链表的 `random`。 |
| 181 | +2. 更新新生成节点的 `random` 指针。 |
| 182 | +3. 恢复原链表的 `random` 指针,同时更新新生成节点的 `next` 指针。 |
| 183 | + |
| 184 | +一图胜千言。 |
| 185 | + |
| 186 | + |
| 187 | + |
| 188 | +相应的代码如下。 |
| 189 | + |
| 190 | +```java |
| 191 | +public Node copyRandomList(Node head) { |
| 192 | + if (head == null) { |
| 193 | + return null; |
| 194 | + } |
| 195 | + Node l1 = head; |
| 196 | + Node l2 = null; |
| 197 | + //生成所有的节点,讲它们保存到原链表的 random 域, |
| 198 | + //同时利用新生成的节点的 next 域保存原链表的 random。 |
| 199 | + while (l1 != null) { |
| 200 | + l2 = new Node(); |
| 201 | + l2.val = l1.val; |
| 202 | + l2.next = l1.random; |
| 203 | + l1.random = l2; |
| 204 | + l1 = l1.next; |
| 205 | + } |
| 206 | + l1 = head; |
| 207 | + //更新新生成节点的 random 指针。 |
| 208 | + while (l1 != null) { |
| 209 | + l2 = l1.random; |
| 210 | + l2.random = l2.next != null ? l2.next.random : null; |
| 211 | + l1 = l1.next; |
| 212 | + } |
| 213 | + |
| 214 | + l1 = head; |
| 215 | + Node l2_head = l1.random; |
| 216 | + //恢复原链表的 random 指针,同时更新新生成节点的 next 指针。 |
| 217 | + while (l1 != null) { |
| 218 | + l2 = l1.random; |
| 219 | + l1.random = l2.next; |
| 220 | + l2.next = l1.next != null ? l1.next.random : null; |
| 221 | + l1 = l1.next; |
| 222 | + } |
| 223 | + return l2_head; |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +# 总 |
| 228 | + |
| 229 | +解法一、解法二是比较直接的想法,直接利用 `HashMap` 存储之前的节点。解法三、解法四利用原有链表的指针,通过指来指去完成了赋值。链表操作的核心思想就是,在改变某一个节点的指针域的时候,一定要把该节点的指针指向的节点用另一个指针保存起来,以免造成丢失。 |
0 commit comments