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

算法思想之链表

欢迎拜访:雾里看山-CSDN博客
本篇主题算法思想之链表
发布时间:2025.4.18
隶属专栏:算法

在这里插入图片描述

目录

  • 算法介绍
    • 常用技巧
  • 例题
    • 两数相加
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现
    • 两两交换链表中的节点
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现
    • 重排链表
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现
    • 合并 K 个升序链表
      • 题目链接
      • 题目描述
      • 算法思路1
      • 代码实现1
      • 算法思路2
      • 代码实现2
    • K 个一组翻转链表
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现

算法介绍

链表(Linked List)是一种基于指针或引用的动态数据结构,通过节点间的链接关系实现数据存储。其核心特点是非连续内存分配与高效增删操作,是算法设计中处理动态数据的核心工具之一。

常用技巧

  1. 画图
    画图可以让我们更加直观、形象的理解具体过程。便于我们的理解
  2. 引入虚拟头结点
    便于我们处理边界情况
    方便我们对链表进行操作
  3. 不要吝啬空间,大胆定义变量
    在进行链表操作时,多定义几个变量更有利于操作
  4. 快慢双指针
    对于判环找链表中环的入口找链表中倒数第n个节点等问题都比较好用

例题

两数相加

题目链接

2. 两数相加

题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1

在这里插入图片描述
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示

  • 每个链表中的节点数在范围 [1, 100]
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

算法思路

两个链表都是逆序存储数字的,即两个链表的个位数、十位数等都已经对应,可以直接相加。

在相加过程中,我们要注意是否产生进位,产生进位时需要将进位和链表数字一同相加。如果产生进位的位置在链表尾部,即答案位数比原链表位数长一位,还需要再 new 一个结点储存最高位。

代码实现

/*** 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* cur1 = l1, *cur2 = l2;ListNode* newhead = new ListNode(0);ListNode* cur = newhead;int num = 0;while(cur1 != nullptr || cur2 != nullptr || num!= 0){if(cur1 != nullptr){num+=cur1->val;cur1 = cur1->next;}if(cur2 != nullptr){num+=cur2->val;cur2 = cur2->next;}                ListNode* tmp = new ListNode(num%10);num/=10;cur->next = tmp;cur = tmp;}cur =  newhead->next;delete newhead;return cur;}
};

在这里插入图片描述

两两交换链表中的节点

题目链接

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

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1

在这里插入图片描述
输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2

输入:head = []
输出:[]

示例 3

输入:head = [1]
输出:[1]
提示

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

算法思路

认真画图,进行模拟即可

代码实现

/*** 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* swapPairs(ListNode* head) {if(head == nullptr || head->next == nullptr)return head;ListNode* newhead = new ListNode(0);newhead->next  = head;ListNode *prev = newhead, *cur = head, *next = head->next, *nnext = next->next;while(cur!=nullptr && next!=nullptr){prev->next = next;next->next = cur;cur->next = nnext;prev = cur;cur = nnext;if(cur)next = cur->next;if(next)nnext = next->next;  }prev = newhead->next;delete newhead;return prev;}
};

在这里插入图片描述

重排链表

题目链接

143. 重排链表

题目描述

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1

在这里插入图片描述
输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2

在这里插入图片描述
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

提示

  • 链表的长度范围为 [1, 5 * 104]
  • 1 <= node.val <= 1000

算法思路

画图画图画图,重要的事情说三遍~

  1. 找中间节点;
  2. 中间部分往后的逆序;
  3. 合并两个链表

代码实现

/*** 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 reorderList(ListNode* head) {if(head == nullptr || head->next ==nullptr || head->next->next == nullptr)  return ;// 1. 找到中间节点ListNode *slow = head,*fast = head;while(fast && fast->next){slow = slow->next;fast=fast->next->next;}// 2. 逆序后半部分ListNode* head2 = new ListNode(0);ListNode *cur = slow->next;slow->next = nullptr;//断开两个链表while(cur){ListNode* next = cur->next;cur->next = head2->next;head2->next=cur;cur = next;}// 3. 合并ListNode *ret = new ListNode(0);ListNode* prev = ret;ListNode *cur1 = head, *cur2 = head2->next;while(cur1){prev->next = cur1;cur1 = cur1->next;prev = prev->next;if(cur2){prev->next = cur2;prev = prev->next;cur2 = cur2->next;}    }delete head2;delete ret;}
};

在这里插入图片描述

合并 K 个升序链表

题目链接

23. 合并 K 个升序链表

题目描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2

输入:lists = []
输出:[]

示例 3

输入:lists = [[]]
输出:[]

提示

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序 排列
  • lists[i].length 的总和不超过 10^4

算法思路1

合并两个有序链表是比较简单且做过的,就是用双指针依次比较链表 1 、链表 2 未排序的最小元素,选择更小的那一个加入有序的答案链表中。

合并 K 个升序链表时,我们依旧可以选择 K 个链表中,头结点值最小的那一个。那么如何快速的得到头结点最小的是哪一个呢?用堆这个数据结构就好啦~

我们可以把所有的头结点放进一个小根堆中,这样就能快速的找到每次 K 个链表中,最小的元素是哪个。

代码实现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 {struct cmp{bool operator()(ListNode* l1, ListNode*l2){return l1->val > l2->val;}};
public:ListNode* mergeKLists(vector<ListNode*>& lists) {priority_queue<ListNode*, vector<ListNode*>, cmp> heap;ListNode *newhead = new ListNode(0);ListNode* cur = newhead;for(auto &iter : lists){if(iter)heap.push(iter);}// 合并k个有序链表while(!heap.empty()){ListNode* tmp = heap.top();cur->next = tmp;heap.pop();cur = tmp;if(tmp->next)heap.push(tmp->next);}cur = newhead->next;delete newhead;return cur;}
};

算法思路2

利用递归和链表合并

  1. 特判,如果题目给出空链表,无需合并,直接返回;
  2. 返回递归结果。
    递归函数设计:
  3. 递归出口:如果当前要合并的链表编号范围左右值相等,无需合并,直接返回当前链表;
  4. 应⽤二分思想,等额划分左右两段需要合并的链表,使这两段合并后的长度尽可能相等;
  5. 对左右两段分别递归,合并[l, r]范围内的链表;
  6. 再调用 mergeTwoLists 函数进行合并(就是合并两个有序链表)

代码实现2

/*** 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* mergeKLists(vector<ListNode*>& lists) {return mergeSort(lists, 0, lists.size()-1);}ListNode* mergeSort(vector<ListNode*>& lists, int left, int right){if(left>right)return nullptr;if(left == right)return lists[left];// 1. 找中间节点int mid = (left + right) >> 1;// 2.递归排序左右链表ListNode* l1 = mergeSort(lists, left, mid);ListNode* l2 = mergeSort(lists, mid + 1, right);// 3. 合并return mergeTowList(l1, l2);}ListNode* mergeTowList(ListNode* l1, ListNode*l2){if(l1 == nullptr) return l2;if(l2 == nullptr) return l1;ListNode *newhead = new ListNode(0);ListNode* cur = newhead;while(l1 && l2){if(l1->val < l2->val){cur->next = l1;l1 = l1->next;cur = cur->next;}else{cur->next = l2;l2 = l2->next;cur = cur->next;}}if(l1) cur->next = l1;if(l2) cur->next = l2;cur = newhead->next;delete newhead;return cur;}
};

在这里插入图片描述

K 个一组翻转链表

题目链接

25. K 个一组翻转链表

题目描述

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1

在这里插入图片描述
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2

在这里插入图片描述
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

算法思路

本题的目标非常清晰易懂,不涉及复杂的算法,只是实现过程中需要考虑的细节比较多。

我们可以把链表按 K 个为一组进行分组,组内进行反转,并且记录反转后的头尾结点,使其可以和前、后连接起来。思路比较简单,但是实现起来是比较复杂的。

我们可以先求出一共需要逆序多少组(假设逆序 n 组),然后重复 n 次长度为 k 的链表的逆序即可。

代码实现

/*** 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* reverseKGroup(ListNode* head, int k) {if(head == nullptr) return nullptr;// 1. 计算需要翻转多少组int n = 0;ListNode* cur = head;while(cur){n++;cur = cur->next;}n /= k;// 2.完成翻转操作ListNode* newhead = new ListNode(0);ListNode* prev = newhead, *next, *tmp;cur = head;while(n--){tmp = cur;int m = k;while(m--){next = cur->next;cur->next = prev->next;prev->next = cur;cur = next; }prev = tmp;}prev->next = cur;cur = newhead->next;delete newhead;return cur; }
};

在这里插入图片描述

⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!

相关文章:

  • 青少年编程与数学 02-016 Python数据结构与算法 27课题、金融算法
  • C++常用锁总结
  • @JsonView + 单一 DTO:如何实现多场景 JSON 字段动态渲染
  • Next.js 技术详解:构建现代化 Web 应用的全栈框架
  • 使用Service发布应用程序
  • 探索C++中的数据结构:栈(Stack)的奥秘
  • 数据类型相关问题导致的索引失效 | OceanBase SQL 优化实践
  • 【C到Java的深度跃迁:从指针到对象,从过程到生态】第二模块·语法迁移篇 —— 第六章 函数革命:从过程到方法的重生
  • 决战浏览器渲染:减少重绘(Repaint)与重排(Reflow)的性能优化策略
  • 在服务器上安装redis
  • vLLM V1:性能优化与集群扩展的深度解析
  • 数据结构基本概念
  • k8s低版本1.15安装prometheus+grafana进行Spring boot数据采集
  • test ssl java
  • Java 序列化与反序列化终极解析
  • pointnet pointnet++论文笔记
  • 麒麟操作系统漏洞修复保姆级教程弱(一)算法漏洞修复
  • Vue3 + TypeScript中provide和inject的用法示例
  • 基于ubuntu24.10安装NACOS2.5.1的简介
  • 第18周:对于ResNeXt-50算法的思考
  • 外交部回应美新任驻日大使涉华言论:外交官的职责不应是抹黑别国、煽动对抗
  • 女外交官杨扬出任中国驻圭亚那大使
  • 日本央行行长:美关税政策将冲击日本经济
  • 小伙称被骗婚骗惨了:存款金条怀孕全是假的,岳父岳母找人演
  • 习近平会见中越人民大联欢活动代表时的致辞