leetcode之hot100---138随机链表的复制(C++)

思路一:迭代+分离链表

  • 在原链表的每个节点的后面复制一个新的节点(包括val/next)形成交错链表

例如:原链表为1->2->3再复制以后就会变成1->1' ->2->2' ->3->3'

  • 根据原链表各个节点的random指向为新链表进行链接

原链表 :

交错链表:

cur->next->random = cur->random->next;

由于多个指针过于乱,因此只用cur->val == 13举例

将交错链表分离成原链表和新建链表,并保留新建链表

浅拷贝与深拷贝:

**浅拷贝:**只在表层创建了新的存储空间,而更深层的引用类型仍然共享内存地址。

  • 只复制对象的第一层属性
  • 如果属性是基本数据类型,复制其值
  • 如果属性是引用类型,复制引用地址(指针)
  • 原对象和新对象会共享引用类型的属性

举例解释浅拷贝:

javascript 复制代码
// 创建一个购物车对象
const cart1 = {
    user: 'Tom',
    items: [
        {name: '手机', price: 1999},
        {name: '耳机', price: 299}
    ],
    totalPrice: 2298
};

// 浅拷贝购物车对象 - 使用扩展运算符
const cart2 = {...cart1};

//两个对象的初始状态相同
console.log('初始状态:');
console.log('cart1:', cart1);
console.log('cart2:', cart2);

// 修改cart2中items数组的内容会导致cart1对应的属性的值改变
// 因为其为引用数据类型,两个对象的数据共用一片地址空间
cart2.items[0].price = 1899;
// 修改cart2中user的内容不会导致cart1对应的属性的值改变
// 因为user属性是基本类型属性,两者存储地址相互独立
cart2.user = 'Jerry';  

console.log('\n修改后:');
console.log('cart1:', cart1);
console.log('cart2:', cart2);

深拷贝(Deep Copy):

  1. 递归复制对象的所有层级属性
  2. 所有引用类型属性都会创建新的副本
  3. 原对象和新对象完全独立,互不影响

举例解释深拷贝:

javascript 复制代码
// 原始商品数据
const product1 = {
    id: 1,
    name: 'iPhone',
    price: 6999,
    category: {
        id: 101,
        name: '手机数码',
        subCategory: '智能手机'
    },
    specs: [
        {
            color: '黑色',
            storage: '128GB'
        },
        {
            color: '白色',
            storage: '256GB'
        }
    ],
    stock: 100,
    isOnSale: true,
    createDate: new Date('2024-01-01'),
    discount: function(percent) {
        return this.price * (1 - percent);
    }
};

// 实现深拷贝函数
function deepClone(obj, hash = new WeakMap()) {
    // 处理null或非对象
    if (obj === null || typeof obj !== 'object') return obj;
    
    // 处理日期对象
    if (obj instanceof Date) return new Date(obj);
    
    // 处理正则对象
    if (obj instanceof RegExp) return new RegExp(obj);
    
    // 处理循环引用
    if (hash.has(obj)) return hash.get(obj);
    
    // 创建新对象或数组
    let clone = Array.isArray(obj) ? [] : {};
    
    // 将对象存入hash表,处理循环引用
    hash.set(obj, clone);
    
    // 递归复制所有属性
    for (let key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            clone[key] = deepClone(obj[key], hash);
        }
    }
    
    return clone;
}

// 创建深拷贝
const product2 = deepClone(product1);

// 测试修改
console.log('初始状态:');
console.log('product1:', product1);
console.log('product2:', product2);

// 修改product2的数据
product2.name = 'iPhone 15';
product2.category.name = '手机';
product2.specs[0].color = '深空黑';
product2.stock = 50;

console.log('\n修改后:');
console.log('product1:', product1);
console.log('product2:', product2);

// 验证两个对象完全独立
console.log('\n验证独立性:');
console.log('name是否相同:', product1.name === product2.name);  // false
console.log('category是否相同:', product1.category === product2.category);  // false
console.log('specs是否相同:', product1.specs === product2.specs);  // false

主要区别:

  1. 复制深度: 浅拷贝一层,深拷贝无限层
  2. 内存地址: 浅拷贝部分共享内存,深拷贝完全独立
  3. 相互影响: 浅拷贝会部分影响原对象,深拷贝完全不影响
  4. 性能: 浅拷贝性能好,深拷贝性能较差

实际应用中的选择:

  • 如果对象结构简单(只有一层),用浅拷贝
  • 如果需要完全独立的副本,用深拷贝
  • 考虑性能要求和实际场景需求

需要注意的是,深拷贝也有一些特殊情况需要处理:

  1. 循环引用问题
  2. 特殊对象类型(Date、RegExp等)
  3. 函数的复制
cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        //链表为空返回空链表
        if(head == nullptr){
            return nullptr;
        }
        //依次复制每个节点至原节点的后面,形成一个交错链表
        for(Node* cur = head; cur; cur = cur->next->next){
            cur->next = new Node(cur->val, cur->next, nullptr);
        }
        //遍历交错链表中的原链表节点
        for(Node* cur = head; cur; cur = cur->next->next){
            if(cur->random){
                //新链表节点的random是random的复制节点
                cur->next->random = cur->random->next;
            }
        }
        //将交错链表分成两个链表
        Node* newHead = head->next;//创建新链表的表头节点,方便返回整个链表
        //获取原链表节点,其中cur指向原链表节点,copy指向复制新链表节点
        Node* cur = head;
        for(; cur->next->next; cur = cur->next){
            Node* copy = cur->next;
            cur->next = copy->next;//获取原链表的下一节点
            copy->next = copy->next->next;//获取复制链表的下一节点
        }
        cur->next = nullptr;
        return newHead;

    }
};
  • 时间复杂度:O(n),其中 n 是链表的长度。
  • 空间复杂度:O(1)。返回值不计入。

思路二:回溯+哈希表

  • 对于当前节点,我们首先检查它是否已经被拷贝过。如果已经拷贝过,我们直接从哈希表中取出对应的新节点返回;如果尚未拷贝,则创建该节点的新副本并记录到哈希表中。
  • 我们递归地拷贝当前节点的后继节点和随机指针指向的节点,确保这两个节点的拷贝已经完成。在递归回溯到当前节点时,我们将新创建节点的后继指针和随机指针分别指向刚刚拷贝完成的对应节点。通过这种方式,我们能够逐步完成整个链表的深度拷贝。
  • 为了处理特殊情况,我们需要特别判断输入的节点是否为空节点。如果为空,直接返回空即可。通过哈希表的记录,我们避免了重复拷贝,并确保每个节点在整个拷贝过程中只被创建一次。
cpp 复制代码
class Solution {
public:
    unordered_map<Node*, Node*> cachedNode;

    Node* copyRandomList(Node* head) {
        if (head == nullptr) {
            return nullptr;
        }
        if (!cachedNode.count(head)) {
            Node* headNew = new Node(head->val);
            cachedNode[head] = headNew;
            headNew->next = copyRandomList(head->next);
            headNew->random = copyRandomList(head->random);
        }
        return cachedNode[head];
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
相关推荐
算AI4 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
懒羊羊大王&5 小时前
模版进阶(沉淀中)
c++
owde6 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
GalaxyPokemon6 小时前
Muduo网络库实现 [九] - EventLoopThread模块
linux·服务器·c++
W_chuanqi6 小时前
安装 Microsoft Visual C++ Build Tools
开发语言·c++·microsoft
hyshhhh6 小时前
【算法岗面试题】深度学习中如何防止过拟合?
网络·人工智能·深度学习·神经网络·算法·计算机视觉
A旧城以西7 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
tadus_zeng7 小时前
Windows C++ 排查死锁
c++·windows
EverestVIP7 小时前
VS中动态库(外部库)导出与使用
开发语言·c++·windows
杉之7 小时前
选择排序笔记
java·算法·排序算法