Offer必备算法12_链表_五道力扣题详解(由易到难)

2024-03-10 02:44

本文主要是介绍Offer必备算法12_链表_五道力扣题详解(由易到难),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

①力扣2. 两数相加

解析代码

②力扣24. 两两交换链表中的节点

解析代码

③力扣143. 重排链表

解析代码

④力扣23. 合并 K 个升序链表

解析代码1(小根堆优化)

解析代码2(递归_归并)

⑤力扣25. K 个一组翻转链表

解析代码

本篇完。


①力扣2. 两数相加

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
  • 题目数据保证列表表示的数字不含前导零
/*** 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) {}
};

解析代码

        两个链表都是逆序存储数字的,即两个链表的个位数、十位数等都已经对应,可以直接相加。 在相加过程中要注意是否产生进位,产生进位时需要将进位和链表数字一同相加。如果产生进位的位置在链表尾部,即答案位数比原链表位数长一位,还需要再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 *head = new ListNode(0);ListNode *ret = head;ListNode *cur1 = l1, *cur2 = l2;int carry = 0; // 进位while(cur1 || cur2 || carry){int val = carry;if(cur1){val += cur1->val;cur1 = cur1->next;}if(cur2){val += cur2->val;cur2 = cur2->next;}head->next = new ListNode(val % 10);head = head->next;carry =  val / 10;}return ret->next;}
};

②力扣24. 两两交换链表中的节点

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) {}
};

解析代码

递归法在下面链接讲过:

Offer必备算法07_递归_五道力扣题详解(由易到难)-CSDN博客

        迭代法就是自己画图,不要吝啬定义指针,直接定义四个指针,在前面new一个头结点视为prev,让cur和next1交换,然后四个指针像后走,结束条件是cur或者next1为空。

/*** 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) {ListNode *newHead = new ListNode(0);if(head == nullptr || head->next == nullptr)return head;// newHead -> 1 -> 2 -> 3// 1和2换 -> cur和next1换// prev -> cur -> next1 -> next2// cur -> prev -> next1 -> next2ListNode *prev=newHead, *cur=head, *next1=head->next, *next2=next1->next;while(cur && next1){prev->next = next1;next1->next = cur;cur->next = next2;prev = cur;cur = next2;if(cur)next1 = cur->next;if(next1)next2 = next1->next;}cur = newHead->next;delete newHead;return cur;// 递归法// if(head == nullptr || head->next == nullptr)//     return head;// ListNode* tmp = swapPairs(head->next->next); // 把两个结点之外的看成另一部分// head->next->next = head;// auto ret = head->next; // 保存一下要返回的结点// head->next = tmp;// return ret;}
};

③力扣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 * 10^4]
  • 1 <= node.val <= 1000
/*** 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) {}
};

解析代码

在C语言讲的数据结构里讲过这三道OJ:

206. 反转链表

876. 链表的中间结点

21. 合并两个有序链表

这道题就是先找到中间结点,分为两部分链表,然后翻转后部分链表,最后合并两个链表:

/*** 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; // 处理边界情况ListNode *fast = head, *slow = head;while(fast && fast->next) // 找到中间结点{slow = slow->next;fast = fast->next->next;}ListNode* slowNext = slow->next;slow->next = nullptr; // 两个链表断开slow = slowNext;ListNode *reverseList = nullptr;while(slow) // 中间结点后面的头插到翻转链表{slowNext = slow->next;slow->next = reverseList;reverseList = slow;slow = slowNext;}ListNode* tmp = head; // 合并两个链表ListNode *cur1 = head->next, *cur2 = reverseList;while(cur1 && cur2){tmp->next = cur2;tmp = tmp->next;cur2 = cur2->next;tmp->next = cur1;cur1 = cur1->next;tmp = tmp->next;}}
};

④力扣23. 合并 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
/*** 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) {}
};

解析代码1(小根堆优化)

        之前写过合并两个有序链表的题,此题暴力解法就是依次合并两个有序链表,合并K次。时间复杂度是O(N*K^2),是N^3级别的。

        在暴力解法基础上可以使用小根堆优化,合并 K 个升序链表时,选择 K 个链表中,头结点值最小的那⼀个的插入到新链表,时间复杂度是O(N*K*logK)。下面是小根堆优化的代码:

/*** 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()(const ListNode* l1, const ListNode* l2){return l1->val > l2->val;}};public:ListNode* mergeKLists(vector<ListNode*>& lists) {priority_queue<ListNode*, vector<ListNode*>, cmp> heap;for(auto& e : lists) // 所有头结点进入小根堆{if(e)heap.push(e);}ListNode* newHead = new ListNode(0);ListNode* tmp = newHead;while(!heap.empty()){   // 尾插堆顶结点到新链表,再把旧链表下个结点插入小根堆ListNode* cur = heap.top();heap.pop();tmp->next = cur;tmp = cur;if(cur->next){heap.push(cur->next);}}tmp = newHead->next;delete newHead;return tmp;}
};

解析代码2(递归_归并)

  1. 递归出口:如果当前要合并的链表编号范围左右值相等,无需合并,直接返回当前链表。
  2. 应用二分思想,等额划分左右两段需要合并的链表,使这两段合并后的长度尽可能相等。
  3. 对左右两段分别递归,合并[left,right]范围内的链表。
  4. 最后调用合并两个链表函数进行合并。
/*** 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 merge(lists, 0, lists.size() - 1);}ListNode* merge(vector<ListNode*>& lists, int left, int right){if(left > right) // 区间不存在return nullptr;if(left == right) // 只有一个链表return lists[left];int mid = (left + right) >> 1;ListNode* list1 = merge(lists, left, mid);ListNode* list2 = merge(lists, mid+1, right);// 上面递归得到一个/两个链表return mergeTwoLists(list1, list2);}ListNode* mergeTwoLists(ListNode* list1, ListNode* list2){if(list1 == nullptr)return list2;if(list2 == nullptr)return list1;if(list1->val <= list2->val){list1->next = mergeTwoLists(list1->next, list2);return list1;}else{list2->next = mergeTwoLists(list2->next, list1);return list2;}}
};

⑤力扣25. 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) 额外内存空间的算法解决此问题吗?

/*** 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) {}
};

解析代码

        可以把链表按 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) {int n = 0;ListNode* cur = head;while(cur){cur = cur->next;++n;}n /= k;cur = head;ListNode* newHead = new ListNode();ListNode* prev = newHead;while(n--){ListNode* tmp = cur;int cnt = k;while(cnt--){ListNode* prevNext = cur->next;cur->next = prev->next; // cur头插到prevprev->next = cur;cur = prevNext;}prev = tmp;}prev->next = cur;cur = newHead->next;delete newHead;return cur;}
};

本篇完。

下一篇动态规划的类型的路径dp类型的OJ。

下下篇数据结构类型的是哈希表类型的OJ。

这篇关于Offer必备算法12_链表_五道力扣题详解(由易到难)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/792769

相关文章

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

详解Java中的敏感信息处理

《详解Java中的敏感信息处理》平时开发中常常会遇到像用户的手机号、姓名、身份证等敏感信息需要处理,这篇文章主要为大家整理了一些常用的方法,希望对大家有所帮助... 目录前后端传输AES 对称加密RSA 非对称加密混合加密数据库加密MD5 + Salt/SHA + SaltAES 加密平时开发中遇到像用户的

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

使用Spring Cache时设置缓存键的注意事项详解

《使用SpringCache时设置缓存键的注意事项详解》在现代的Web应用中,缓存是提高系统性能和响应速度的重要手段之一,Spring框架提供了强大的缓存支持,通过​​@Cacheable​​、​​... 目录引言1. 缓存键的基本概念2. 默认缓存键生成器3. 自定义缓存键3.1 使用​​@Cacheab