LeetCode 热题 100 精讲 | 链表篇:反转链表·环形链表·有序链表·LRU

一、206. 反转链表

🔗 题目链接

LeetCode 206. 反转链表

📝 题目描述

给你单链表的头节点 head,请你反转链表,并返回反转后的链表。

示例

复制代码
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

🧠 思路分析

反转链表是链表题中最基础也最重要的一道。迭代法需要维护三个指针:prev 指向已反转部分的尾节点,curr 指向当前待反转的节点,next 暂存 curr 的下一个节点以免断链。每次循环让 curr->next = prev,然后三个指针整体后移一步。递归法更简洁但需要理解递归栈:递归到链表末尾,然后让每个节点的下一个节点的 next 指向自己,再把自己的 next 置空。两种方法的时间复杂度都是 O(n),空间上迭代 O(1) 更优,递归 O(n) 的栈空间。面试中通常要求写迭代版本。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr) {
            ListNode* next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
};

📚 相关学习资源

  • 免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n),遍历一次链表。

  • 空间复杂度:O(1),只用了常数个指针。


二、141. 环形链表

🔗 题目链接

LeetCode 141. 环形链表

📝 题目描述

给你一个链表的头节点 head,判断链表中是否有环。如果链表中有某个节点可以通过连续跟踪 next 指针再次到达,则链表中存在环。返回 truefalse

示例

复制代码
输入:head = [3,2,0,-4],尾部连接到下标 1
输出:true
解释:链表中有一个环,尾部连接到第二个节点。

🧠 思路分析

最经典的解法是快慢指针,也叫 Floyd 判圈算法。慢指针每次走一步,快指针每次走两步。如果链表无环,快指针会先到达 nullptr。如果有环,快指针最终会追上慢指针(因为每走一步它们的距离就缩小 1)。这个解法不需要额外空间,时间复杂度 O(n)。哈希表法也可以做,但需要 O(n) 空间,面试时推荐快慢指针。注意边界条件:空链表或只有一个节点且 next 指向自己时也要正确处理。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (!head || !head->next) return false;
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) return true;
        }
        return false;
    }
};
复制代码
 

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n),最坏情况每个节点被访问两次。

  • 空间复杂度:O(1),只用了两个指针。


三、21. 合并两个有序链表

🔗 题目链接

LeetCode 21. 合并两个有序链表

📝 题目描述

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例

复制代码
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

🧠 思路分析

可以像合并两个有序数组一样,用双指针比较两个链表当前节点的值,将较小的节点接到结果链表的末尾,然后移动对应指针。需要一个虚拟头节点(dummy node)来简化边界处理,避免单独判断头节点为空的情况。当其中一个链表遍历完时,直接把另一个链表的剩余部分接上。递归写法也很漂亮:比较两个头节点,较小的节点的 next 指向剩余部分合并的结果,然后返回该节点。递归的空间复杂度是 O(n + m) 的栈空间,迭代是 O(1)。这道题是归并排序中"合并"步骤的链表版本。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode dummy(0);
        ListNode* tail = &dummy;
        while (list1 && list2) {
            if (list1->val < list2->val) {
                tail->next = list1;
                list1 = list1->next;
            } else {
                tail->next = list2;
                list2 = list2->next;
            }
            tail = tail->next;
        }
        tail->next = list1 ? list1 : list2;
        return dummy.next;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n + m),每个节点被访问一次。

  • 空间复杂度:O(1)(迭代版本),不使用额外空间。


四、146. LRU 缓存

🔗 题目链接

LeetCode 146. LRU 缓存

📝 题目描述

请你设计并实现一个满足 LRU(最近最少使用)缓存约束的数据结构。实现 LRUCache 类:LRUCache(int capacity) 以正整数初始化容量;int get(int key) 如果关键字存在,返回其值,否则返回 -1;void put(int key, int value) 如果关键字已存在则修改值,否则插入。如果插入导致超过容量,则逐出最久未使用的关键字。getput 的平均时间复杂度要求 O(1)。

示例

复制代码
输入:
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出:[null, null, null, 1, null, -1, null, -1, 3, 4]

🧠 思路分析

需要在 O(1) 时间内完成查找、插入、删除,以及维护访问顺序。哈希表加双向链表是标准解法。哈希表负责 O(1) 的键值查找,键映射到链表节点;双向链表维护访问顺序,靠近头部的节点是最近使用的,靠近尾部的节点是最久未使用的。get 操作:从哈希表找到节点,将该节点移到链表头部,返回值。put 操作:如果 key 存在,更新值并将节点移到头部;如果 key 不存在,在头部插入新节点,然后检查是否超出容量,超出则删除尾部节点并删除哈希表中的对应项。双向链表方便 O(1) 删除任意节点,单向链表做不到。面试时手写双向链表的增删操作是常规要求,也可以直接用 STL 的 list,但建议自己实现以展现对原理的理解。

💻 代码实现(C++)

cpp 复制代码
class LRUCache {
private:
    struct Node {
        int key, val;
        Node* prev;
        Node* next;
        Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {}
    };

    int cap;
    unordered_map<int, Node*> cache;
    Node* head;
    Node* tail;

    void removeNode(Node* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    void addToHead(Node* node) {
        node->next = head->next;
        node->prev = head;
        head->next->prev = node;
        head->next = node;
    }

    void moveToHead(Node* node) {
        removeNode(node);
        addToHead(node);
    }

    Node* removeTail() {
        Node* node = tail->prev;
        removeNode(node);
        return node;
    }

public:
    LRUCache(int capacity) : cap(capacity) {
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head->next = tail;
        tail->prev = head;
    }

    int get(int key) {
        if (cache.find(key) == cache.end()) return -1;
        Node* node = cache[key];
        moveToHead(node);
        return node->val;
    }

    void put(int key, int value) {
        if (cache.find(key) != cache.end()) {
            Node* node = cache[key];
            node->val = value;
            moveToHead(node);
        } else {
            Node* newNode = new Node(key, value);
            cache[key] = newNode;
            addToHead(newNode);
            if (cache.size() > cap) {
                Node* removed = removeTail();
                cache.erase(removed->key);
                delete removed;
            }
        }
    }
};

📚 相关学习资源

免责声明 :以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度getput 均为 O(1),哈希表查找 O(1),链表操作 O(1)。

  • 空间复杂度:O(capacity),哈希表和链表最多存储 capacity 个节点。


结语

链表篇的四道题覆盖了链表操作的核心技能:指针修改 (反转链表)、双指针技巧 (环形链表)、归并思想 (合并两个有序链表)、数据结构组合设计(LRU 缓存)。其中 LRU 缓存是面试中出现频率极高的设计题,手写双向链表的能力需要反复练习。

建议刷题顺序:先做反转链表和合并两个有序链表练手感,再做环形链表理解快慢指针,最后攻克 LRU 缓存。下一篇将进入 二叉树篇,敬请期待。

如果本文对你有帮助,欢迎点赞、收藏、转发,你的支持是我持续创作的动力 ❤️

免责声明:本文部分解题思路参考了力扣官方题解及社区优秀文章,相关视频链接均来自公开网络。若存在侵权问题,请联系删除。

相关推荐
Yzzz-F4 小时前
Problem - 2146D1 - Codeforces &&Problem - D2 - Codeforces
算法
Kk.08024 小时前
力扣 LCR 084.全排列||
算法·leetcode·职场和发展
环黄金线HHJX.4 小时前
龙虾钳足启发的AI集群语言交互新范式
开发语言·人工智能·算法·编辑器·交互
Omics Pro4 小时前
虚拟细胞:开启HIV/AIDS治疗新纪元的关键?
大数据·数据库·人工智能·深度学习·算法·机器学习·计算机视觉
旖-旎5 小时前
分治(快速选择算法)(3)
c++·算法·leetcode·排序算法·快速选择
_日拱一卒5 小时前
LeetCode:合并区间
算法·leetcode·职场和发展
xiaoye-duck5 小时前
【C++:哈希表封装】哈希表封装 myunordered_map/myunordered_set 实战:底层原理 + 完整实现
数据结构·c++·散列表
汀、人工智能5 小时前
[特殊字符] 第3课:最长连续序列
数据结构·算法·数据库架构·图论·bfs·最长连续序列
少许极端5 小时前
算法奇妙屋(四十一)-贪心算法学习之路 8
学习·算法·贪心算法
A.A呐5 小时前
【C++第二十三章】C++11
开发语言·c++