Leetcode 刷题记录 09 —— 链表第三弹

本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答,01~07为C++语言,08及以后为Java语言。

01 合并 K 个升序链表

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        
    }
    
    
}

方法一:遍历 + 合并两个有序链表

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        int k = lists.length;
        ListNode dummy = null;
        for(int i=0; i<k; i++){
            dummy = mergeTwoLists(dummy, lists[i]);
        }
        return dummy;
    }

    //合并两个有序链表
    public ListNode mergeTwoLists(ListNode l1, ListNode l2){
        ListNode l3 = new ListNode(0);

        ListNode flag = l3; //return flag.next
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                l3.next = l1;
                l1 = l1.next;
            }else{
                l3.next = l2;
                l2 = l2.next;
            }
            l3 = l3.next;
        }

        if(l1 != null){
            l3.next = l1;
        }
        if(l2 != null){
            l3.next = l2;
        }

        return flag.next;
    }
}

方法二:二分法(递归)+ 合并两个有序链表

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists, 0, lists.length-1);
    }

    public ListNode merge(ListNode[] lists, int left, int right){
        if(left == right){
            return lists[left];
        }

        if(left > right){
            return null; //为啥捏?
        }

        int mid = (left + right) / 2;
        return mergeTwoLists(merge(lists, left, mid), merge(lists, mid+1, right));
    }

    //合并两个有序链表
    public ListNode mergeTwoLists(ListNode l1, ListNode l2){
        ListNode l3 = new ListNode(0);

        ListNode flag = l3; //return flag.next
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                l3.next = l1;
                l1 = l1.next;
            }else{
                l3.next = l2;
                l2 = l2.next;
            }
            l3 = l3.next;
        }

        if(l1 != null){
            l3.next = l1;
        }
        if(l2 != null){
            l3.next = l2;
        }

        return flag.next;
    }
}

在什么情况下left > right,为什么要返回null值?

当你调用 mergeKLists 时,假设 lists 为空数组,即 lists.length == 0,那么初始调用 merge(lists, 0, -1) 就会发生 left > right 的情况,在这种情况下,返回 null 是合理的,因为没有链表可以合并。

02 LRU 缓存

java 复制代码
class LRUCache {

    public LRUCache(int capacity) {
        
    }
    
    public int get(int key) {
        
    }
    
    public void put(int key, int value) {
        
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

方法:哈希映射 + 双向链表

java 复制代码
class LRUCache {
    //1.自定义双向链表结点
    class DLinkedNode{
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int _key, int _value){
            this.key = _key;
            this.value = _value;
        }
    }

    //2.自定义哈希映射 cache <int, DLinkedNode>
    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size, capacity;
    private DLinkedNode head, tail;

    /*
    3.初始化LRU缓存
        a.初始化 size, capacity
        b.初始化 head, tail
    */
    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;

        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }
    
    /*
    4.查询操作:如果key在cache中,返回value;否则,返回-1
        a. node = null, return -1
        b. node != null, move to head, return node.value
    */
    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if(node == null){
            return -1;
        }
            
        /*
        move to head
            a. remove node
            b. add to head
        */
        node.prev.next = node.next;
        node.next.prev = node.prev;

        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;

        return node.value;
    }
    
    /*
    5.更新操作:如果key在cache中,更新value;否则,插入cache <key, nodeNew>
        a. node = null
            i.创建 nodeNew
            ii.插入 cache <key, nodeNew>, add to head
            iii. size > capacity, remove tail
        b. node != null, move to head
    */
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if(node == null){
            DLinkedNode nodeNew = new DLinkedNode(key, value);
            cache.put(key, nodeNew);
            ++size;

            // add to head
            nodeNew.prev = head;
            nodeNew.next = head.next;
            head.next.prev = nodeNew;
            head.next = nodeNew;

            if(size > capacity){
                DLinkedNode res = tail.prev;

                //remove node
                res.prev.next = res.next;
                res.next.prev = res.prev;

                cache.remove(res.key);
                --size;
            }
        }
        else{
            node.value = value;

            /*
            move to head
                a. remove node
                b. add to head
            */
            node.prev.next = node.next;
            node.next.prev = node.prev;

            node.prev = head;
            node.next = head.next;
            head.next.prev = node;
            head.next = node;
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

① 为什么要采用双向链表,而不是单向链表?

  • 快速删除:双向链表可以从链表中的任何位置快速移除节点,因为每个节点都有一个指向前驱节点的指针,而单向链表则需要从头开始遍历才能找到前驱节点。
  • 移动节点到头部:在LRU缓存中,频繁的操作是将节点移动到链表头部。如果采用单向链表,这个操作会更复杂且效率低下,而双向链表可以在常数时间内完成。

② 为什么要将DLinkedNode定义为内部类?

  • 封装DLinkedNode仅在LRUCache中使用,将其作为内部类可以更好地实现封装。
  • 简化代码:在内部类中可以直接访问外部类的私有成员和方法,简化了代码的结构,而无需额外的getter/setter。
  • 逻辑关系清晰DLinkedNode本质上是LRUCache的一部分,通过内部类的方式可以更明确地表达这种包含关系。
相关推荐
吃好睡好便好5 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
仰泳之鹅6 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
x_yeyue8 小时前
三角形数
笔记·算法·数论·组合数学
念何架构之路9 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星9 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑9 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode
黎阳之光9 小时前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
丷丩10 小时前
三级缓存下MVT地图瓦片服务性能优化策略
算法·缓存·性能优化·gis·geoai-up
m0_6294947310 小时前
LeetCode 热题 100-----25.回文链表
数据结构·算法·leetcode·链表
ʚ希希ɞ ྀ11 小时前
单词拆分----dp
算法