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

相关文章

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

SQL Server 中的 WITH (NOLOCK) 示例详解

《SQLServer中的WITH(NOLOCK)示例详解》SQLServer中的WITH(NOLOCK)是一种表提示,等同于READUNCOMMITTED隔离级别,允许查询在不获取共享锁的情... 目录SQL Server 中的 WITH (NOLOCK) 详解一、WITH (NOLOCK) 的本质二、工作

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原