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的一部分,通过内部类的方式可以更明确地表达这种包含关系。
相关推荐
Kidddddult7 分钟前
力扣刷题Day 37:LRU 缓存(146)
算法·leetcode·力扣
生信碱移38 分钟前
TCGA数据库临床亚型可用!贝叶斯聚类+特征网络分析,这篇 NC 提供的方法可以快速用起来了!
人工智能·python·算法·数据挖掘·数据分析
wang__123001 小时前
力扣1812题解
算法·leetcode·职场和发展
_Power_Y2 小时前
面试算法刷题练习1(核心+acm)
算法·面试
Leo来编程2 小时前
算法-时间复杂度和空间复杂度
算法
阳洞洞2 小时前
leetcode 141. Linked List Cycle
数据结构·leetcode·链表·双指针
Echo``2 小时前
2:点云处理—3D相机开发
人工智能·笔记·数码相机·算法·计算机视觉·3d·视觉检测
星夜9823 小时前
C++回顾 Day5
开发语言·c++·算法
JK0x073 小时前
代码随想录算法训练营 Day37 动态规划Ⅴ 完全背包 零钱兑换
算法·动态规划
weixin_464078073 小时前
.NET 多线程题目汇总
算法·.net