4.链表的学习(下)

普通链表的复制

链表节点的定义:

// Definition for a Node.
class Node {
    int val;
    Node next, random;
    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}

给定链表的头节点head,复制普通链表很简单,只需遍历链表,每轮建立新节点 + 构建前驱节点pre和当前节点node的引用指向即可。这里用到了伪头部节点!!!

class Solution {
    public Node copyRandomList(Node head) {
        Node cur = head;
        // 伪造头部节点dum
        Node dum = new Node(0), pre = dum;
        while(cur != null) {
            Node node = new Node(cur.val); // 复制节点 cur
            pre.next = node;               // 新链表的 前驱节点 -> 当前节点
            cur = cur.next;                // 遍历下一节点
            pre = node;                    // 保存当前新节点
        }
        // 返回的伪节点的下一个节点
        return dum.next;
    }
}

复杂链表的复制

// Definition for a Node.
class Node {
    int val;
    Node next, random;
    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}

在这里插入图片描述
本题难点: 在复制链表的过程中构建新链表各节点的 random 引用指向。
在普通链表的复制中,是可以直接通过前驱节点连接新建立的节点。而复杂节点中的random引用指向采用上面的思路是不能构建的,因为random所指向的节点此时还没有建立。
此时能想到的一个方法就是哈希大法,哈希表具有查询的功能,那么将原链表和新链表之间通过哈希表k-v方式建立联系,就可以自然寻找到random和next引用指向了

class Solution {
    public Node copyRandomList(Node head) {

        // 使用哈希表:构建原链表和新链表之间的联系
        // 特判
        if(head == null){
            return null;
        }
        HashMap<Node,Node> map = new HashMap<>();
        // 建立遍历指针
        Node cur = head;
        while(cur != null){
            Node node = new Node(cur.val);
            // 储存原节点 和 新节点
            map.put(cur,node);
            // 遍历指针下移
            cur = cur.next;
        }
        // 已经构造好新链表节点,开始建立next和random引用指向
        cur = head;
        while(cur != null){
            // next引用指向:新节点指向新节点
            map.get(cur).next = map.get(cur.next);
            // random引用指向
            map.get(cur).random = map.get(cur.random);
            // cur指针下移
            cur = cur.next;
        }
        // 返回新链表的head节点
        return map.get(head);
        
    }
}

LRU缓存设计中涉及链表操作

// 其中head为伪头部,tail为伪尾部,这是双向链表的常规操作!
// 插入到头部(这里只能利用head)
    public void addToHead(DLinkedList node){
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next= node;
    }
    // 移动到头部
    public void moveToHead(DLinkedList node){
        // 先删除该节点
        node.prev.next = node.next;
        node.next.prev = node.prev;
        //插入到头部
        addToHead(node);
    }
    // 删除最后一个节点(只能用tail)
    public DLinkedList deleteLastNode(){
        DLinkedList res = tail.prev;
        res.prev.next = tail;
        tail.prev = res.prev;
        return res;
    }

在链表操作这里尤其要警惕指针丢失和内存泄露

在这里插入图片描述
上图中,要先将x的next指针指向p的next指针,再将p的next指针指向x。一定要注意顺序!!!

利用伪节点(哨兵节点)进行增删改

在这里插入图片描述

边界条件的处理

这里不单单指链表,在写代码的时候都要考虑边界条件,比如空指针、空值问题。
反映到链表就是链表为空,代码是否能工作,链表只有头结点,代码能否正常工作…

画图

在这里插入图片描述
要在图形中去思考顺序问题,特殊情况问题。比如LRU设计中,移动到头部节点时,只能用head指针进行操作(因为head和tail中间还有未知的节点,所以只能选择head),单单看伪头节点和伪尾结点很容易被迷惑,直接操作tail节点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值