PS:《剑指offer》是很多同学找工作都会参考的一本面试指南,同时也是一本算法指南(为什么它这么受欢迎,主要应该是其提供了一个循序渐进的优化解法,这点我觉得十分友好)。现在很多互联网的算法面试题基本上可以在这里找到影子,为了以后方便参考与回顾,现将书中例题用Java实现(第二版),欢迎各位同学一起交流进步。
GitHub: https://github.com/Uplpw/SwordOffer。
剑指offer完整题目链接: https://blog.csdn.net/qq_41866626/article/details/120415258
1 题目描述
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
leetcode链接: 复杂链表的复制(以下代码已测试,提交通过)
2 测试用例
一般是考虑功能用例,特殊(边缘)用例或者是反例,无效测试用例这三种情况。甚至可以从测试用例寻找一些规律解决问题,同时也可以让我们的程序更加完整鲁棒。
(1)功能用例:正常的链表,多节点。
(2)边缘用例:单节点链表。
(3)无效用例:链表为空。
3 思路
分析:
注意: 这里的复制链表是指重新开辟内存创建对象进行操作,而不是单单对原来内存的引用进行操作。
下面介绍几种解决方法:
解法1:
最简单直接的解法,先复制链表的值val和下一个指针next(遍历链表一次),之后再复制随机指针random(这里需要循环遍历两次:外层循环是给所有节点复制随机指针,内层循环是根据原始链表随机指针指向的值在新的链表找到其位置。)
时间空间复杂度: O ( n 2 ) , O ( 1 ) O(n^2),O(1) O(n2),O(1)(除去必须创建对象占用的空间)
解法2:
对解法1进行优化,利用hash存储旧链表节点到新链表节点的映射,这样就可以直接通过hash根据旧链表的随机指针找到在新链表对应的随机指针,注意随机指针为空的情况。(空间换时间)
时间空间复杂度: O ( n ) , O ( n ) O(n),O(n) O(n),O(n)(除去必须创建对象占用的空间)
4 代码
复杂链表:
public class ComplexListNode {
int val;
ComplexListNode next;
ComplexListNode random;
public ComplexListNode(int val) {
this.val = val;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
ComplexListNode cur = this;
while (cur != null) {
ret.append(cur.val);
if (cur.random != null)
ret.append("(" + cur.random.val + ")");
else {
ret.append("(_)");
}
ret.append('\t');
cur = cur.next;
}
return ret.toString();
}
}
算法实现:
import java.util.HashMap;
public class ComplexListNodeClone {
// 解法1:简单解法
public static ComplexListNode copyRandomList1(ComplexListNode head) {
if (head == null)
return null;
// 新链表的头节点
ComplexListNode newHead = new ComplexListNode(head.val);
// 旧链表的当前节点
ComplexListNode current = head.next;
// 新链表创建的新节点
ComplexListNode newCurrent = null;
// 新链表创建的新节点的上一个节点
ComplexListNode newCurrentPrev = newHead;
// 复制旧链表节点值和下一个指针
while (current != null) {
// 创建新节点
newCurrent = new ComplexListNode(current.val);
// 连接新节点
newCurrentPrev.next = newCurrent;
// 指针移向下一个节点
newCurrentPrev = newCurrentPrev.next;
// 旧链表指针移向下一个节点
current = current.next;
}
current = head;
newCurrent = newHead;
ComplexListNode temp = head;
ComplexListNode newTemp = newHead;
// 使用双重循环复制旧链表随机指针
while (current != null) {
// 判断随机指针是否为空
if (current.random != null) {
temp = head;
newTemp = newHead;
while (temp != current.random) {
// 新旧指针一起往后移动
temp = temp.next;
newTemp = newTemp.next;
}
newCurrent.random = newTemp;
}
// 为下一个节点复制随机指针
current = current.next;
newCurrent = newCurrent.next;
}
return newHead;
}
// 解法2:利用hash
public static ComplexListNode copyRandomList2(ComplexListNode head) {
if (head == null) {
return null;
}
// 利用hash将新旧链表节点一一对应起来
HashMap<ComplexListNode, ComplexListNode> map = new HashMap<>();
ComplexListNode newHead = new ComplexListNode(head.val);
map.put(head, newHead);
ComplexListNode newCurrent = newHead;
ComplexListNode newNode = null;
ComplexListNode current = head.next;
// 先复制旧链表的值和下一个指针
while (current != null) {
newNode = new ComplexListNode(current.val);
map.put(current, newNode);
newCurrent.next = newNode;
newCurrent = newCurrent.next;
current = current.next;
}
current = head;
newCurrent = newHead;
// 再复制旧链表的随机指针
while (current != null) {
if (current.random != null) {
newCurrent.random = map.get(current.random);
}
current = current.next;
newCurrent = newCurrent.next;
}
return newHead;
}
public static void main(String[] args) {
ComplexListNode head = new ComplexListNode(1);
ComplexListNode c2 = new ComplexListNode(2);
ComplexListNode c3 = new ComplexListNode(3);
ComplexListNode c4 = new ComplexListNode(4);
ComplexListNode c5 = new ComplexListNode(5);
head.next = c2;
head.random = c3;
head.next.next = c3;
head.next.random = c5;
head.next.next.next = c4;
head.next.next.next.next = c5;
head.next.next.next.random = c2;
System.out.println("original:" + '\t' + head);
System.out.println("clone1: " + '\t' + copyRandomList1(head));
}
}
参考
在解决本书例题时,参考了一些大佬的题解,比如leetcode上的官方、K神,以及其他的博客,在之后的每个例题详解后都会给出参考的思路或者代码链接,同学们都可以点进去看看!
本例题参考:
本文如有什么不足或不对的地方,欢迎大家批评指正,最后希望能和大家一起交流进步、拿到心仪的 offer !!!