当前位置: 首页 > news >正文

【LeetCode 热题 100】链表 系列

📁206. 反转链表

        对于每一个节点cur,都是将next节点指向cur,cur节点指向上一个节点head。因此可以采用递归的策略,从后往前进行上述操作,期间记录最后一个节点并返回。

我是将递归分为3类:

1. 前序递归:在递归之前进行处理。

2. 中序递归:在递归中进行处理。

3. 后续递归:在递归后处理。

        本地就是一个后续递归的操作,地递归到最后一层,从后往前进行处理。

ListNode* reverseList(ListNode* head) {if(head == nullptr || head->next == nullptr)return head;ListNode* newHead = reverseList(head->next);ListNode* next = head->next;head->next = next->next;next->next = head;return newHead;}

📁160. 相交链表

        我们假设A链表总结点个数是a,B链表总结点个数是b,如果存在公共节点node,那么我们假设公共节点后面所有节点个数是c。

        指针 cur1 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:

                                                                 a+(b−c)

        指针 cur2 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:

                                                                b+(a−c)

        那么可以列式,并有两种情况:

                                                        a+(b−c)=b+(a−c)

        1. 如果两链表有公共节点(c > 0):节点 cur1 和 cur2 指向公共节点node

        2. 如果不存在公共节点(c = 0):节点 cur1 和 cur2 都指向nullptr

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode* cur1 = headA , *cur2 = headB;while(cur1 != cur2){cur1 = cur1 != nullptr ? cur1->next : headB;cur2 = cur2 != nullptr ? cur2->next : headA;}return cur1;}

📁234. 回文链表

        一个简单的思路就是取出链表中的元素,放到数组中,使用双指针判断数组是否是回文。

bool isPalindrome(ListNode* head) {vector<int> ret;while(head){ret.push_back(head->val);head = head->next;}int right = ret.size() - 1 , left = 0;while(left < right){if(ret[left++] != ret[right--])return false;}return true;}

📁141. 环形链表

        我们来假设一种情况,在一条无限长的跑道上,小明每秒走x步,小妹每秒走x+1步,并且小美在小明后面追赶,如果情况成立小美一定能追上小明,因为他们之间的距离每次都1。

        对于该题目,采用双指针思路,如果链表有环,那么slow指针进入换后就一定能被fast指针追上。

bool hasCycle(ListNode *head) {ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast)return true;}return false;}

📁142. 环形链表 II

符号定义与初始条件​

  • ​头节点​​(head):链表的起点。
  • ​环入口节点​​(A):链表开始形成环的节点。
  • ​相遇点​​(B):快慢指针第一次相遇的位置。
  • ​a​​:头节点到环入口节点的距离。
  • ​b​​:环入口节点到相遇点的距离。
  • ​c​​:相遇点到环入口节点的剩余距离。
  • ​环周长​​:n = b + c(环的总长度)。
  • ​快指针速度​​:每次移动 2 步。
  • ​慢指针速度​​:每次移动 1 步。

                                                Sfast​=2⋅Sslow​⟹a+k(b+c)+b=2(a+b)

将上述方程化简:

                                        a+k(b+c)+b=2a+2b⟹k(b+c)=a+b⟹ a=(k−1)(b+c)+c

  • ​ 等于 ​​c​​ 加上 (k - 1) 圈环的周长。
  • 若 k = 1,则 a = c,即从头节点到环入口的距离等于相遇点到环入口的距离。
ListNode *detectCycle(ListNode *head) {ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast){while(slow != head){slow = slow->next;head = head->next;}return head;}}return nullptr;}

📁2. 两数相加

        本题就是一道类似高精度的问题,但是本题相较简单,我们只需要从后往前遍历即可,每次取出两个个位数相加,对结果进行模除操作结果放到新节点中即可。

        此外,需要注意的是,最后需要判断最后一位数相加是否还需要进位。

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode* newHead = new ListNode();ListNode* tail = newHead;int tmp = 0;while(l1 != nullptr || l2 != nullptr){if(l1){tmp += l1->val;l1 = l1->next;}if(l2){tmp += l2->val;l2 = l2->next;}tail->next = new ListNode(tmp % 10);tmp /= 10;tail = tail->next;}if(tmp)tail->next = new ListNode(tmp);tail = newHead->next;delete newHead;return tail;}

📁 21.  合并两个有序链表

        最简单的一个思路就是创建一个新的链表,然后遍历两个链表,将较小值的节点尾插到新节点中。

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {ListNode* cur1 = list1 , *cur2 = list2;ListNode* newHead = new ListNode();ListNode* tail = newHead;while(cur1 && cur2){if(cur1->val < cur2->val){tail->next = cur1;tail = tail->next;cur1 = cur1->next;}else{tail->next = cur2;tail = tail->next;cur2 = cur2->next;}}while(cur1){tail->next = cur1;tail = tail->next;cur1 = cur1->next;}while(cur2){tail->next = cur2;tail = tail->next;cur2 = cur2->next;}tail = newHead->next;delete newHead;return tail;}

       也可以采用递归的方法。

1. 递归函数的返回值:返回合并后链表的头结点。

2. 递归函数结束条件:如果有一个链接节点为空,返回另一个节点。

3. 递归过程:比较两个链表的节点,取出较小值作为当前轮次的头结点,它的next指向递归的返回值,即两个链表剩下节点合并后链表的头结点。

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(!list1)  return list2;if(!list2)  return list1;if(list1->val < list2->val)list1->next = mergeTwoLists(list1->next , list2);else list2->next = mergeTwoLists(list1 , list2->next);return list1->val < list2->val ? list1 : list2;}

📁 19. 删除链表的倒数第 N 个结点

        从头节点开始对链表进行一次遍历,得到链表的长度 L。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 L− n + 1 个节点时,它就是我们需要删除的节点。

ListNode* removeNthFromEnd(ListNode* head, int n) {int sz = 0;ListNode* cur = head;while(cur){++sz;cur = cur->next;}//创建新的头结点方便处理只有一个节点的特殊情况ListNode* newhead = new ListNode( 0 , head);cur = newhead;for(int i = 0 ; i < sz - n; ++i){cur = cur->next;}cur->next = cur->next->next;return newhead->next;}

📁 24. 两两交换链表中的节点

        本题我们可以看成反转链表的一种变种,只不过是变成两两交换了,第一个节点head , 第二个节点next,head节点next指向剩余链表中交换后的链表的头结点,next节点的next指向head节点。

ListNode* swapPairs(ListNode* head) {if(!head || !head->next)return head;ListNode* next = head->next;head->next = swapPairs(next->next);next->next = head;return next;}   

📁 25. K 个一组翻转链表

        就是将 n / k 个组内的节点进行头插,然后将剩下的节点在链入链表中即可。

ListNode* reverseKGroup(ListNode* head, int k) {ListNode* newHead = new ListNode();ListNode* tail = newHead;    //组内最后一个节点ListNode* cur = head;int n = 0;while(cur){++n , cur = cur->next;}n /= k;cur = head;for(int i = 0 ; i < n ; ++i){//每组第一个节点经过翻转后变成最后一节点 ListNode* tmp = cur;for(int j = 0 ; j < k ; ++j){   ListNode* next = cur->next;cur->next = tail->next;tail->next = cur;cur = next;}tail = tmp;}if(cur)tail->next = cur;return newHead->next;}

📁 138. 随机链表的复制

        对于本题最苦难的地方就在于随机指针的指向问题了,因为random可能指向一个还没有创建的节点node,如果我们直接创建,并使得random指向node,那么node之前的节点的next指针怎么指向node,因此我们需要一个东西来记录下来。

        我们可以使用unordered_map来记录原节点和新节点的对应关系,这样random指向一个没有创建的节点node时,node可以直接创建,并且node节点之前的节点prev的next指针可以通过hash找到node节点,并成功指向。

class Solution {
public:unordered_map<Node* , Node*> hash;Node* copyRandomList(Node* head) {if(head == nullptr)return head;if(!hash.count(head)){Node* newNode = new Node(head->val);hash[head] = newNode;newNode->next = copyRandomList(head->next);newNode->random = copyRandomList(head->random);}return hash[head];}
};

 📁 148. 排序链表

        最简单的方法就是节点排序,然后创建新的链表,按值大小从后往前链接节点。我们使用map记录值和节点直接的映射关系,因为底层是红黑树,即平衡二叉搜索树,所以遍历时是从小到大遍历的,multimap可以保证我们存在相同值。

ListNode* sortList(ListNode* head) {if(head == nullptr || head->next == nullptr)return head;multimap<int , ListNode*> hash;ListNode* cur = head;while(cur){   hash.insert(make_pair(cur->val , cur));cur = cur->next;}ListNode* newHead = new ListNode();ListNode* tail = newHead;for(auto& [key , node] : hash){tail->next = node;tail = tail->next;}tail->next = nullptr;return  newHead->next;}

📁 23. 合并 K 个升序链表

        对于链表,我可以将翻转看成将每个节点从后往前进行头插。那么本题就简单了,我们只需要头插节点即可。

        "如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序",可知我们需要翻转组数目 = n / k。

        此外,进行头插后,我们还需要记录下来每组第一个节点,每组进行反转后第一个节点变为了最后一个节点,它的next节点应该指向下一组翻转节点。

struct cmp{bool operator()(const ListNode* l1 , const ListNode* l2){return l1->val > l2->val;}};ListNode* mergeKLists(vector<ListNode*>& lists) {std::priority_queue<ListNode* , std::vector<ListNode*> , cmp> heap;for(ListNode* node : lists)if(node != nullptr)heap.push(node);ListNode* newHead = new ListNode();ListNode* tail = newHead;while(!heap.empty()){ListNode* node = heap.top(); heap.pop();tail->next = node;tail = tail->next;if(node->next != nullptr)heap.push(node->next);}return newHead->next; }

📁 146. LRU 缓存

        本题就是将将新插入或刚被调用的元素插入到链表头部,当容量满时淘汰掉链表尾部元素,即最近最少被使用的元素。

        为什么使用unorder_map,因为哈希系列查找速度是O(1),而链表的查找速度是O(N),为了提高效率我们使用哈希来查找存储 <key,val>元素的链表节点的iterator。

class LRUCache {
public:typedef list<pair<int,int>>::iterator it;LRUCache(int capacity) {_capacity = capacity;}//判断是否缓存//找到了 将其剪切到链表的头部位置int get(int key) {auto kv = _hash.find(key);if(kv != _hash.end()){//push过 已被缓存_list.splice(_list.begin() , _list , kv->second);return kv->second->second;}return -1;}//1. 首先查找LRU中是否缓存了//2. 如果存在, 更新val 并且放到链表的最前方//3. 如果不存在 在判断链表是否未满//i. 如果链表满了 尾删//ii. 其次 头插并记录映射void put(int key, int value) {auto kv = _hash.find(key);if(kv != _hash.end()){//更新kv->second->second = value;_list.splice(_list.begin() , _list , kv->second);}else{if(_list.size() == _capacity){//尾删一个元素_hash.erase(_list.back().first);_list.pop_back();}//头插新元素_list.emplace_front(key , value); _hash[key] = _list.begin();}}private:int _capacity = 0;unordered_map<int , it> _hash;list<pair<int , int>> _list;
};

相关文章:

  • [实战] 卡尔曼滤波:原理、推导与卫星导航应用仿真(完整代码)
  • 深入剖析 TypeScript 基础类型:string、number、boolean 的声明与使用
  • lnmp1.5+centos7版本安装php8
  • ※※惯性时间常数与系统惯量定义、区别、联系
  • 数据结构手撕--【堆】
  • 【matlab】绘制maxENT模型的ROC曲线和omission curve
  • Java基础 — 循环
  • 深入解析 C++17 中的std::variant与std::visit:从原理到实践
  • Python函数基础:说明文档(多行注释),函数嵌套调用,变量作用域(局部,全局,global关键字),综合案例
  • PMP-第一章 引论
  • Linux 复制、移动命令总结
  • ADC介绍
  • Android 13 接入 MediaSession 详细文档
  • DP之书架
  • CANFD技术在实时运动控制系统中的应用:协议解析、性能测试与未来发展趋势
  • 数据可视化大屏——大数据分析系统
  • 【人工智能】Python中的深度学习模型部署:从训练到生产环境
  • 前端面试宝典---vue实现简化版
  • 用Xshell8配置密钥登陆
  • olama部署deepseek模型
  • 夜读丨庭院春韵
  • 商务部:入境消费增长潜力巨大,离境退税有助降低境外旅客购物成本
  • 2025厦门体育产业采风活动圆满举行
  • 5145篇报道中的上海车展:40年,什么变了?
  • 旧衣服旧纸箱不舍得扔?可能是因为“囤物障碍”
  • 经济日报:上海车展展现独特魅力