一、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. 环形链表
🔗 题目链接
📝 题目描述
给你一个链表的头节点 head,判断链表中是否有环。如果链表中有某个节点可以通过连续跟踪 next 指针再次到达,则链表中存在环。返回 true 或 false。
示例:
输入: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. 合并两个有序链表
🔗 题目链接
📝 题目描述
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入: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;
}
};
📚 相关学习资源
-
题解 :力扣21:合并两个有序链表
-
题解 :【LeetCode题解】21_合并两个有序链表(迭代+递归,Java/Python)](https://www.shuzhiduo.com/A/xl561387Jr/)
免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。
⏱ 复杂度分析
-
时间复杂度:O(n + m),每个节点被访问一次。
-
空间复杂度:O(1)(迭代版本),不使用额外空间。
四、146. LRU 缓存
🔗 题目链接
📝 题目描述
请你设计并实现一个满足 LRU(最近最少使用)缓存约束的数据结构。实现 LRUCache 类:LRUCache(int capacity) 以正整数初始化容量;int get(int key) 如果关键字存在,返回其值,否则返回 -1;void put(int key, int value) 如果关键字已存在则修改值,否则插入。如果插入导致超过容量,则逐出最久未使用的关键字。get 和 put 的平均时间复杂度要求 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;
}
}
}
};
📚 相关学习资源
免责声明 :以上链接均来自公开网络。若存在侵权问题,请联系删除。
⏱ 复杂度分析
-
时间复杂度 :
get和put均为 O(1),哈希表查找 O(1),链表操作 O(1)。 -
空间复杂度:O(capacity),哈希表和链表最多存储 capacity 个节点。
结语
链表篇的四道题覆盖了链表操作的核心技能:指针修改 (反转链表)、双指针技巧 (环形链表)、归并思想 (合并两个有序链表)、数据结构组合设计(LRU 缓存)。其中 LRU 缓存是面试中出现频率极高的设计题,手写双向链表的能力需要反复练习。
建议刷题顺序:先做反转链表和合并两个有序链表练手感,再做环形链表理解快慢指针,最后攻克 LRU 缓存。下一篇将进入 二叉树篇,敬请期待。
如果本文对你有帮助,欢迎点赞、收藏、转发,你的支持是我持续创作的动力 ❤️
免责声明:本文部分解题思路参考了力扣官方题解及社区优秀文章,相关视频链接均来自公开网络。若存在侵权问题,请联系删除。