数据结构-链表

article/2025/11/9 13:00:08

链表

  • 一、介绍
    • 1、单链表
      • 1、单链表结构体:
      • 2、单链表头插法:
      • 3、单链表尾插法:
  • 二、例题
    • 1、双指针(获取倒数第K个元素、获取中间位置的元素、判断链表是否存在环、判断环的长度、查找第一个公共节点、回文链表)
      • 1、 判断链表是否存在环
        • 1、hot100 - 141. 环形链表(快慢指针)
        • 2、hot100 - 142. 环形链表 II(快慢指针)【科大讯飞】
      • 2、判断环的长度
      • 3、获取倒数第K个元素
        • 1、hot100 - 19. 删除链表的倒数第 N 个结点(NC53 删除链表的倒数第n个节点)【蔚来面试】
      • 4、查找第一个公共节点
        • 1、*hot100 - 160. 相交链表(剑指 Offer 52. 两个链表的第一个公共节点)
      • 5、回文链表
        • 1、*hot100 - 234. 回文链表
      • 6、剑指 Offer II 077. 链表排序【科大讯飞】
        • 6.1 自底向上归并排序
        • 6.2 自顶向下归并排序
    • 2、一般解法
      • 1、hot100 - 2. 两数相加
      • 2、*hot100 - 206. 反转链表(剑指 Offer 24. 反转链表、NC78 反转链表)
      • 3、hot100 - 21. 合并两个有序链表(剑指 Offer 25. 合并两个排序的链表、NC33 合并两个排序的链表)
      • 4、NC50 链表中的节点每k个一组翻转(leetcode - 25. K 个一组翻转链表)
        • 方法一
        • 方法二
    • 3、双链表
      • 1、*hot100 - 146. LRU 缓存(NC93 设计LRU缓存结构)

一、介绍

1、单链表

1、单链表结构体:

struct ListNode {int val;ListNode *next;ListNode():val(0), next(nullptr){}ListNode(int x):val(x), next(nullptr){}ListNode(int x, ListNode *next):val(x), next(next){}
};

2、单链表头插法:

ListNode* create(vector<int> &ve){ListNode *head = nullptr;for(int each: ve){if(!head) head = new ListNode(each);else{ListNode *tmp = new ListNode(each);ListNode *p = head->next;head->next = tmp;tmp->next = p;}}   return head;
}

3、单链表尾插法:

ListNode* create(vector<int> &ve){ListNode *head = nullptr, *s = nullptr;for(int each: ve){if(!head) head = s = new ListNode(each);else{ListNode *p = new ListNode(each);s->next = p;s = s->next;}}   return head;
}

二、例题

1、双指针(获取倒数第K个元素、获取中间位置的元素、判断链表是否存在环、判断环的长度、查找第一个公共节点、回文链表)

1、 判断链表是否存在环

1、hot100 - 141. 环形链表(快慢指针)

题目:https://leetcode-cn.com/problems/linked-list-cycle/

  • 判断是否有环
    • 快慢双指针,如果有环两指针肯定会相遇;
    • 如果快指针先越界了,无环;
  • 证明:慢指针速度为1,快指针速度为2,快慢指针一定能相遇(数学归纳法
    • 快指针与慢指针之间差一步。此时继续往后走,慢指针前进一步,快指针前进两步,二者相遇。
    • 快指针与慢指针之间两一步。此时继续往后走,慢指针前进一步,快指针前进两步,两者之间相差一步,转为第一种情况。
    • 快指针与慢指针之间差N步。此时继续往后走,慢指针前进一步,快指针前进两步,两者之间相差 N-1步。转化为上一种情况。
    • 因此若存在环,快指针必然与慢指针相遇。
  • 时间复杂度:O(N)
    • 当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。
    • 当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮。
  • 空间复杂度:O(1)。只使用了两个指针的额外空间。
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:bool hasCycle(ListNode *head) {if(head == nullptr || head->next==nullptr) return false;ListNode *slow = head, *quick = head->next;while(slow != quick){if(quick == nullptr) return false;slow = slow->next;if(quick->next == nullptr) return false;quick = quick->next->next;}return true;}
};

2、hot100 - 142. 环形链表 II(快慢指针)【科大讯飞】

题目:https://leetcode-cn.com/problems/linked-list-cycle-ii/

  • 思路与算法
    • 使用两个指针,quick与slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而quick指针向后移动两个位置。如果链表中存在环,则quick指针最终将再次与slow 指针在环中相遇。
    • 如下图所示,设链表中环外部分的长度为 a。slow 指针进入环后,又走了b的距离与quick相遇。此时,quick指针已经走完了环的n圈,因此它走过的总距离为 a+n(b+c)+b=a+(n+1)b+nc。
      在这里插入图片描述
    • 根据题意,任意时刻,quick指针走过的距离都为slow 指针的2倍。因此,a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
    • 有了 a=c+(n-1)(b+c)a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上n−1圈的环长,恰好等于从链表头部到入环点的距离。
    • 因此,当发现slow 与quick相遇时,再额外使用一个指针pre。起始,它指向链表头部;随后,它和slow每次向后移动一个位置。最终,它们会在入环点相遇。
  • 时间复杂度:O(N)。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N)。
  • 空间复杂度:O(1)。只使用了 slow,fast,pre三个指针。
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {
public:ListNode *detectCycle(ListNode *head) {ListNode *slow = head, *quick = head;while(quick != nullptr){if(quick->next == nullptr) return nullptr;slow = slow->next;quick = quick->next->next;if(slow == quick){  //慢指针、快指针相遇ListNode *pre = head;while(pre != slow){slow = slow->next;pre = pre->next;}return pre;}}return nullptr;}
};

2、判断环的长度

  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
class Solution {
public:bool hasCycle(ListNode *head) {ListNode *slow = head;ListNode *fast = head;while (fast && fast->next) {fast = fast->next->next;slow = slow->next;if (slow == fast) break;}int length = 1;slow = slow->next;while (slow != fast) {slow = slow->next;length++;}return length;}
};

3、获取倒数第K个元素

1、hot100 - 19. 删除链表的倒数第 N 个结点(NC53 删除链表的倒数第n个节点)【蔚来面试】

NC53 删除链表的倒数第n个节点
题目:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

  • 时间复杂度:O(L),L链表长度
  • 空间复杂度:O(1)
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode *p = head, *pre = head, *n_p = head;int k = 0;while(p!=nullptr){if(k >= n){pre = n_p;n_p = n_p->next;}p = p->next;k++;}if(n_p == pre){head = pre->next;}else{pre->next = n_p->next;}return head;}
};

官网代码:

class Solution {
public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummy = new ListNode(0, head);ListNode* first = head;ListNode* second = dummy;for (int i = 0; i < n; ++i) {first = first->next;}while (first) {first = first->next;second = second->next;}second->next = second->next->next;ListNode* ans = dummy->next;delete dummy;return ans;}
};

4、查找第一个公共节点

1、*hot100 - 160. 相交链表(剑指 Offer 52. 两个链表的第一个公共节点)

题目:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

  • 解法:
    • 首先判断链表headA和headB 是否为空,如果其中至少有一个链表为空,则两个链表一定不相交,返回null
    • 当链表headA和headB都不为空时,创建两个指针node1和node2,初始时分别指向两个链表的头节点headA和headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:
      • 每步操作需要同时更新指针node1和node2。
      • 如果指针node1不为空,则将指针node1移到下一个节点;如果指针node2不为空,则将指针node2移到下一个节点。
      • 如果指针node1为空,则将指针node1移到链表headB的头节点;如果指针node2为空,则将指针node2移到链表headA的头节点。
      • 当指针node1和node2指向同一个节点或者都为空时,返回它们指向的节点或者null。
  • 证明:
    • 情况一:两个链表相交
    • 链表headA和headB的长度分别是m和n。假设链表headA 的不相交部分有a个节点,链表headB的不相交部分有b个节点,两个链表相交的部分有c个节点,则有 a+c=m,b+c=n。
      • 如果a=b,则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
      • 如果a!=b,则指针node1会遍历完链表headA,指针node2会遍历完链表headB,两个指针不会同时到达链表的尾节点,然后指针node1移到链表headB的头节点,指针node2 移到链表headA的头节点,然后两个指针继续移动,在指针node1移动了 a+c+b次、指针node2移动了 b+c+a次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。
    • 情况二:两个链表不相交
    • 链表headA和headB的长度分别是m和n。考虑当m=n和m!=n时,两个指针分别会如何移动:
      • 如果m=n,则两个指针会同时到达两个链表的尾节点,然后同时变成空值null,此时返回null;
      • 如果m!=n,则由于两个链表没有公共节点,两个指针也不会同时到达两个链表的尾节点,因此两个指针都会遍历完两个链表,在指针node1移动了m+n次、指针node2移动了n+m次之后,两个指针会同时变成空值null,此时返回null。
  • 时间复杂度:O(m+n)
  • 空间复杂度:O(1)
class Solution {
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode *node1 = headA;ListNode *node2 = headB;while (node1 != node2) {node1 = node1 != NULL ? node1->next : headB;node2 = node2 != NULL ? node2->next : headA;}return node1;}
};

5、回文链表

1、*hot100 - 234. 回文链表

题目:https://leetcode-cn.com/problems/palindrome-linked-list/

  • 解法:
    1. 找到前半部分链表的尾节点(使用快慢双指针);
    2. 反转后半部分链表(头插法/将当前指针的next指向前一个指针),参见反转链表;
    3. 判断是否回文(比较前后两部分链表的值,当后半部分达到末尾则比较完成);
    4. 恢复链表(头插法/将当前指针的next指向前一个指针),参见反转链表;
    5. 返回结果。
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:void reverseList(ListNode *head){if(!head) return ;ListNode *h_next = head->next;head->next = nullptr;while(h_next){ListNode *p = h_next->next;h_next->next = head->next;head->next = h_next;h_next = p;}}bool isPalindrome(ListNode* head) {if(!head) return true;//快慢指针找到中间值ListNode *slow = head, *fast = head->next;while(fast && fast->next){slow = slow->next;fast = fast->next->next;}ListNode *mid = slow;//翻转后半部分的链表reverseList(mid);//判断是否回文slow = head;fast = mid->next;bool res = true;while(fast){if(slow->val != fast->val){res = false;break;}slow = slow->next;fast = fast->next;}//还原链表reverseList(mid);return res;}
};

6、剑指 Offer II 077. 链表排序【科大讯飞】

题目:https://leetcode.cn/problems/7WHec2/
二路归并排序

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)

6.1 自底向上归并排序

在这里插入图片描述

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* sortList(ListNode* head) {if(head == nullptr) return head;int length = 0;ListNode *p = head;while(p != nullptr){length ++;p = p->next;}ListNode *rehead = new ListNode(0, head);for(int sublength = 1; sublength < length; sublength <<= 1){ListNode *pre = rehead, *cur = rehead->next;while(cur != nullptr){ListNode *head1 = cur;for(int i=1; i<sublength && cur->next!=nullptr; i++){cur = cur->next;}ListNode *head2 = cur->next;cur->next = nullptr; // head1的结尾cur = head2;for(int i=1; i<sublength && cur!=nullptr && cur->next!=nullptr; i++){cur = cur->next;}ListNode *next = nullptr;if(cur != nullptr){next = cur->next;cur->next = nullptr; }ListNode *merged = merge(head1, head2);pre->next = merged;while(pre->next != nullptr){pre = pre->next;}cur = next;}}head = rehead->next;delete rehead;return head; }ListNode *merge(ListNode *head1, ListNode*head2){ListNode *head = new ListNode(0);ListNode *p = head;ListNode *h1 = head1, *h2 = head2;while(h1!=nullptr && h2!=nullptr){if(h1->val <= h2->val){p->next = h1;h1 = h1->next;}else{p->next = h2;h2 = h2->next;}p = p->next;}p->next = h1 != nullptr ? h1 : h2;p = head->next;delete head;return p;}
};

6.2 自顶向下归并排序

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)。取决于递归调用的栈空间。
    在这里插入图片描述
class Solution {
public:ListNode* sortList(ListNode* head) {return sortList(head, nullptr);}// 区间:前闭后开ListNode *sortList(ListNode *head, ListNode *tail){if(head == nullptr) return head;// 这种情况不需要再找中点if(head->next == tail){ head->next = nullptr;return head;}// 双指针,快慢指针法ListNode *slow = head, *fast = head;while(fast != tail){slow = slow->next;fast = fast->next;if(fast != tail) fast = fast->next;}// slow即为中点,区间:前闭后开return merge(sortList(head, slow), sortList(slow, fast));}ListNode *merge(ListNode *head1, ListNode *head2){ListNode *head = new ListNode();ListNode *h = head, *h1 = head1, *h2 = head2;while(h1 != nullptr && h2 != nullptr){if(h1->val <= h2->val){h->next = h1;h1 = h1->next;}else{h->next = h2;h2 = h2->next;}h = h->next;}h->next = h1 != nullptr ? h1 : h2;ListNode  *rhead = head->next;delete head;return rhead;}
};

2、一般解法

1、hot100 - 2. 两数相加

题目:https://leetcode-cn.com/problems/add-two-numbers/

  • 时间复杂度:O(max(m,n))
  • 空间复杂度:O(1)。注意返回值不计入空间复杂度
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/class Solution {
public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode *head = nullptr, *tail = nullptr;int carry = 0;while (l1 || l2) {int n1 = l1 ? l1->val: 0;int n2 = l2 ? l2->val: 0;int sum = n1 + n2 + carry;if (!head) {head = tail = new ListNode(sum % 10);} else {tail->next = new ListNode(sum % 10);tail = tail->next;}carry = sum / 10;if (l1) {l1 = l1->next;}if (l2) {l2 = l2->next;}}if (carry > 0) {tail->next = new ListNode(carry);}return head;}
};

2、*hot100 - 206. 反转链表(剑指 Offer 24. 反转链表、NC78 反转链表)

题目:https://leetcode-cn.com/problems/reverse-linked-list/

  • 题解(迭代)

    • 在遍历链表时,将当前节点的next 指针改为指向前一个节点。
  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode *pre = nullptr;ListNode *cur = head;while(cur){ListNode *tmp = cur->next;cur->next = pre;pre = cur;cur = tmp;}return pre;}
};

3、hot100 - 21. 合并两个有序链表(剑指 Offer 25. 合并两个排序的链表、NC33 合并两个排序的链表)

题目:https://leetcode-cn.com/problems/merge-two-sorted-lists/

  • 时间复杂度:O(m+n)
  • 空间复杂度:O(1)
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(list1==nullptr && list2==nullptr) return nullptr;if(list1!=nullptr && list2==nullptr) return list1;if(list1==nullptr && list2!=nullptr) return list2;ListNode* head = nullptr, *p = nullptr;//尾插法while(list1!=nullptr && list2!=nullptr){if(list1->val < list2->val){if(!head) p = head = list1;ListNode *tmp = list1->next;p->next = list1;p = p->next;list1 = tmp;}else{if(!head) p = head = list2;ListNode *tmp = list2->next;p->next = list2;p = p->next;list2 = tmp;}}p->next = list1==nullptr? list2: list1;return head;}
};

创建一个头节点

class Solution {
public:ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {ListNode *pHead3 = new ListNode(-1);ListNode *p = pHead1, *q = pHead2, *t = pHead3;while(p != nullptr && q != nullptr){ListNode *tmp = nullptr;if(p->val <= q->val){tmp = p;p = p->next;}else{tmp = q;q = q->next;}t->next = tmp;t = t->next;}t->next = p!=nullptr ? p : q;ListNode *tmp = pHead3; pHead3 = pHead3->next;delete tmp;return pHead3;}
};

4、NC50 链表中的节点每k个一组翻转(leetcode - 25. K 个一组翻转链表)

题目:https://www.nowcoder.com/practice/b49c3dc907814e9bbfa8437c251b028e?tpId=117&tqId=37746&rp=1&ru=/exam/oj&qru=/exam/oj&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D117&difficulty=undefined&judgeStatus=undefined&tags=&title=

方法一

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)。O(n/k),最坏情况下k=1。
/*** struct ListNode {*	int val;*	struct ListNode *next;* };*/class Solution {
public:ListNode* reverseKGroup(ListNode* head, int k) {// write code here//找到每次翻转的尾部ListNode *tail = head;//遍历k次到尾部for(int i=0; i<k; i++){//如果不足k到了链表尾,直接返回,不翻转if(tail==nullptr) return head;tail = tail->next;}ListNode *p = head->next, *pre = head;while(p!=tail){ListNode *tmp = p->next;p->next = head;head = p;p = tmp;}//当前尾指向下一段要翻转的链表pre->next = reverseKGroup(tail, k);return head;}
};

方法二

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
class Solution {
public:// 翻转一个子链表,并且返回新的头与尾pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {ListNode* prev = tail->next;ListNode* p = head;while (prev != tail) {ListNode* nex = p->next;p->next = prev;prev = p;p = nex;}return {tail, head};}ListNode* reverseKGroup(ListNode* head, int k) {ListNode* hair = new ListNode(0);hair->next = head;ListNode* pre = hair;while (head) {ListNode* tail = pre;// 查看剩余部分长度是否大于等于 kfor (int i = 0; i < k; ++i) {tail = tail->next;if (!tail) {return hair->next;}}ListNode* nex = tail->next;// 这里是 C++17 的写法,也可以写成// pair<ListNode*, ListNode*> result = myReverse(head, tail);// head = result.first;// tail = result.second;tie(head, tail) = myReverse(head, tail);// 把子链表重新接回原链表pre->next = head;tail->next = nex;pre = tail;head = tail->next;}return hair->next;}
};

3、双链表

1、*hot100 - 146. LRU 缓存(NC93 设计LRU缓存结构)

题目: https://leetcode.cn/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
方法: 哈希表+双向链表

  • LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
    • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
    • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。
  • 这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)的时间内完成 get 或者 put 操作。具体的方法如下:
    • 对于 get 操作,首先判断 key 是否存在:
      • 如果 key 不存在,则返回 -1;
      • 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
    • 对于 set 操作,首先判断 key 是否存在:
      • 如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
      • 如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。
  • 上述各项操作中,访问哈希表的时间复杂度为O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为O(1)。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1)时间内完成。
  • 在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。
  • get和set时间复杂度:O(1)
// get操作:如果key存在,先通过哈希表定位,再移到头部;如果不存在,返回-1
// put操作:
//     1.如果key不存在,创建一个新节点,添加至双链表的头部,如果超出容量,删除双链表的尾部节点
//     2.如果key存在,先通过哈希表定位,再修改value,并移动到头部struct DLinkedNode{int key, value;DLinkedNode *pre, *next;DLinkedNode():key(0), value(0), pre(nullptr), next(nullptr){}DLinkedNode(int _key, int _value):key(_key), value(_value), pre(nullptr), next(nullptr){}
};class Solution {
private:unordered_map<int, DLinkedNode*> cache;DLinkedNode *head;DLinkedNode *tail;int size;int capacity;
public:Solution(int capacity):capacity(capacity), size(0){// 使用伪头部和伪尾部节点head = new DLinkedNode();tail = new DLinkedNode();head->next = tail;tail->pre = head;// write code here}int get(int key) {// write code here// 如果key不存在,返回-1if(!cache.count(key)){return -1;}else{// 如果key存在,先通过哈希表获取key的位置,返回对应的value值,再将key移动到双链表表头.    // 如果 key 存在,先通过哈希表定位,再移到头部DLinkedNode *cur = cache[key];moveHead(cur);return cur->value;}}void set(int key, int value){// write code here// 如果缓存中不存在key, 新建一个节点// 判断容器是否装满,如果未装满,直接将新建的节点添加至表头,// 如果装满,将新建节点添加至表头,再删除末尾节点并删除在哈希表中的对应项// 如果 key 不存在,创建一个新的节点if(!cache.count(key)){DLinkedNode *cur = new DLinkedNode(key, value);// 添加进哈希表cache[key] = cur;// 添加至双向链表的头部addToHead(cur);size++;if(size > capacity){// 如果超出容量,删除双向链表的尾部节点DLinkedNode *node = removeTail();// 删除哈希表中对应的项cache.erase(node->key);// 防止内存泄漏delete node;size--;}}else{// 如果 key 存在,先通过哈希表定位,再修改value,并移到头部DLinkedNode *cur = cache[key];cur->value = value;moveHead(cur);}}void addToHead(DLinkedNode *node){node->pre = head;node->next = head->next;head->next->pre = node;head->next = node;}void removeHead(DLinkedNode *node){node->pre->next = node->next;node->next->pre = node->pre;}void moveHead(DLinkedNode *node){removeHead(node);addToHead(node);}DLinkedNode* removeTail(){DLinkedNode *node = tail->pre;removeHead(node);return node;}
};/*** Your Solution object will be instantiated and called as such:* Solution* solution = new Solution(capacity);* int output = solution->get(key);* solution->set(key,value);*/

http://chatgpt.dhexx.cn/article/4k5ISZqE.shtml

相关文章

C语言数据结构、十字链表的分析及实现

目录 前言 一、什么是十字链表 二、认识十字链表 1.十字链表的组成 2.顶点和弧的连接 三、代码逻辑实现 1.出度 2.入度 总结 前言 无论是什么程序都要和数据打交道&#xff0c;一个好的程序员会选择更优的数据结构来更好的解决问题&#xff0c;因此数据结构的重要性不言…

JS 数据结构:链表

单链表 每个节点中只包含一个指针域的链表称为单链表。 头结点—其指针域指向表中第一个结点的指针&#xff08;头结点不是必须的&#xff0c;只是习惯上加上头结点&#xff0c;而头结点的数据域一般记录的是该链表的相关数据&#xff0c;如&#xff1a;链表长度&#xff09;…

数据结构中链表和列表的区别

顺序表和链表由于存储结构上的差异&#xff0c;导致它们具有不同的特点&#xff0c;适用于不同的场景。通过系统地学习顺序 表和链表我们知道&#xff0c;虽然它们同属于线性表&#xff0c;但数据的存储结构有本质的不同。 • 顺序表存储数据&#xff0c;需预先申请一整块足够…

数据结构(六)——循环链表

一、循序链表简介 1、循环链表的定义 循环链表的任意元素都有一个前驱和一个后继&#xff0c;所有数据元素在关系上构成逻辑上的环。 循环链表是一种特殊的单链表&#xff0c;尾结点的指针指向首结点的地址。 循环链表的逻辑关系图如下&#xff1a; 2、循环链表的设计实现 …

数据结构-使用链表实现栈

目录结构 Stack接口 package LinkedListStack;public interface Stack<E> {int getSize();boolean isEmpty();void push(E e); //向栈中添加元素E pop();//向栈中取出元素E peek();//查看栈顶的元素 }LinkedList类 package LinkedListStack;public class LinkedList<…

数据结构 | 链表的实现

目录 单链表双链表数组结构和链式结构的对比 线性表中&#xff0c;除了顺序表这一重要的结构&#xff0c;还有链式结构&#xff0c;而这也是我们常说的链表。 一般是通过定义结构体(类)的方式来表示链表的每一个结点。一般而言&#xff0c;链表的结点都会有数据域和地址域。数据…

Java数据结构之链表

目录 一.单链表 1.单链表的介绍和内存布局 2.单链表的添加和遍历 3.单链表的插入 4.单链表的删除 二.双向链表 1.添加节点 2.遍历节点 3.插入节点 4.删除结点 5.测试 三.单向环形链表 1.问题的引出 ​编辑 2.构建环形链表 1.创建结点 3.测试 3.约瑟夫问题代码的…

c++数据结构:链表

链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成&#xff0c;结点可以在运行时动态生成。每个结点包括两个部分&#xff1a;一个是…

java数据结构-链表详解

文章目录 1.数据结构-链表详解1.1单链表1.1.1单链表节点的尾部添加1.1.2单链表节点的自动排序添加1.1.3单链表节点的修改1.1.4单链表节点的删除 1.2单链表面试题1.2.1单链表的有效节点个数1.2.2单链表倒数第k个结点1.2.3单链表反转1.2.4单链表逆序打印 1.3双向链表1.3.1双向链表…

C语言数据结构链表(图文)

目录 一、链表的简单理解与引入 1.1 链表的引入 1.2 节点的理解 1.3 链表的分类 二、常用链表功能的实现 2.1 首先是节点的定义&#xff0c; 2.2 节点的创建 2.3 单链表的尾插 2.4 单链表的尾删 2.5 单链表的头插 2.6 链表的头删 2.7 单…

【数据结构】链表的学习总结

目录 1.链表的概念2.链表的结构1️⃣链表中单个结点的结构2️⃣链表的整体结构 3.链表的分类4.链表的实现1️⃣单向无头非循环2️⃣双向带头循环 1.链表的概念 链表&#xff0c;是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针…

C++数据结构之链表(详解)

主要参考文章地址 01.链表基础知识 | 算法通关手册 (itcharge.cn)&#xff09; 本次内容是对链表的总结&#xff0c;可以看了上面的文章之后。 在看我下面的内容&#xff0c;做一个简短的复习&#xff0c;且本内容的代码均用C实现&#xff0c;而参考资料的代码则为python。 …

[数据结构]链表之单链表(详解)

文章目录 [数据结构]链表之单链表前言1.链表1.1链表的概念及结构1.2单链表与顺序表的区别与优缺点1.3八种链表类型、单向带头循环链表单向带头非循环链表单向不带头循环链表单向不带头非循环链表双向带头循环链表双向带头非循环链表双向不带头循环链表双向不带头非循环链表 2.单…

【数据结构与算法】详解什么是链表,并用代码手动实现一个链表结构

本系列文章【数据结构与算法】所有完整代码已上传 github&#xff0c;想要完整代码的小伙伴可以直接去那获取&#xff0c;可以的话欢迎点个Star哦~下面放上跳转链接 https://github.com/Lpyexplore/structureAndAlgorithm-JS 本文将来讲解一下一种常见的线性数据结构—链表&a…

数据结构-链表篇

数据结构中数组和链表是是使用频率最高的基础数据结构。数组作为数据存储结构有一定的缺陷。在无序数组中&#xff0c;搜索性能差&#xff0c;在有序数组中&#xff0c;插入效率又很低&#xff0c;而且这两种数组的删除效率都很低&#xff0c;并且数组在创建后&#xff0c;其大…

数据结构之——链表

目录 一、链表的概念及结构 二、单链表的实现&#xff08;无头单向非循环链表&#xff09; 1.单链表节点定义 2.单链表的接口实现 &#xff08;1&#xff09;动态申请一个节点 &#xff08;2&#xff09;单链表打印 &#xff08;3&#xff09;单链表的销毁 &#xff0…

【数据结构】链表

单链表 这张图是我们待会要实现的功能&#xff0c;我会尽可能的将每一步都说的很详细&#xff0c;方便理解。 链表的概念及结构 概念&#xff1a;链表是一种 物理存储结构上非连续 、非顺序的存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 指针链 接 次序实现的 。…

数据结构之链表

目录 一、链表的特点 二、虚拟头结点 三、链表的实现 1、定义LinkedList 2、 构造方法 3、基本方法 4、添加元素 5、查找元素 6、修改元素 7、删除元素 链表是一种物理存储单元上非连续、非顺序的数据结构。前几篇我们讲到的数组也好&#xff0c;基于数组实现的栈…

[数据结构] 链表(图文超详解讲解)

文章目录 一、链表是什么&#xff1f;二、链表 1.链表的结构2.链表方法的代码实现总结 一、链表是什么&#xff1f; 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 二、链表 1.链表的结构 链表的结构如图: 链…

数据结构---单向链表,双向链表,单向环形链表

链表介绍 链表是以节点的方式来存储,是链式存储每个节点包含 data 域&#xff0c; next 域:指向下一个节点.如图:发现链表的各个节点不一定是连续存储.链表分带头节点的链表和没有头节点的链表&#xff0c;根据实际的需求来确定 修改节点功能 思路(1) 先找到该节点&#xff0c…