leetcode 经典题分类
- 链表
- 数组
- 字符串
- 哈希表
- 二分法
- 双指针
- 滑动窗口
- 递归/回溯
- 动态规划
- 二叉树
- 辅助栈
本系列专栏:点击进入 leetcode题目分类 关注走一波
前言 :本系列文章初衷是为了按类别整理
出力扣(leetcode)最经典 题目,一共100多道题,每道题都给出了非常详细的解题思路 、算法步骤 、代码实现 。很多同学刚开始刷题都是按照力扣顺序刷题,其实这样对新手不太适用,刷题效果也很不好。因为力扣题目顺序是随机的,并没有按照算法分类,导致同一类型的算法强化训练不够,最后刷完也是迷迷糊糊的。所以本系列文章就是来帮你完成算法分类,针对每种算法做强化训练,保证让你以后遇到题目直接秒杀!
文章目录
- [leetcode 经典题分类](#leetcode 经典题分类)
两数相加
【题目描述】
给你两个非空 的链表,表示两个非负的整数。它们每位数字都是按照逆序 的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。
【输入输出实例】
示例 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]
【算法思想】
建立一个新的链表,利用while循环直到指向链表1和链表2的指针全部为空为止,利用三目运算符来获取链表1和链表2的某结点的数值(如果链表1或链表2为空,则对应数值为0);
将获取到两链表对应的结点数值做加法运算(不要忘记+进位),同时更新下一次加法计算的进位,创建新的结点加入到链表中(利用尾插法,需要提前创建头指针和尾指针)。
最后while循环结束后,需要判断是否还有进位存在,来选择是否创建新的结点加入到链表中。
【算法描述】
cpp
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
ListNode* head = new ListNode(0); //头指针指向虚拟头结点
ListNode* tail = head; //尾指针------------用于不断插入新节点(尾插法)
int carry = 0; //表示进位
while(l1 || l2) //创建一个新链表
{
int num1 = l1 ? l1->val : 0;
int num2 = l2 ? l2->val : 0;
int sum = num1 + num2 + carry; //对应两数相加(包括进位)
carry = sum / 10; //进位
ListNode* p = new ListNode(sum % 10); //创建新结点
tail->next = p; //链接到新链表末尾
tail = tail->next; //更新tail尾指针
if(l1) //继续向后相加l1和l2对应的数
{
l1 = l1->next;
}
if(l2)
{
l2 = l2->next;
}
}
if(carry != 0) //当最后还有进位时,直接链接到新链表后
{
tail->next = new ListNode(carry);
tail = tail->next;
}
return head->next;
}
删除链表的倒数第N个结点
【题目描述】
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。尝试使用一趟扫描实现。
【输入输出实例】
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
【算法思路】
可以使用两个指针left和right同时对链表进行遍历,并且使得right比left超前n个节点。当right遍历到链表的末尾时,left就恰好处于倒数第n个节点。
(1)创建虚头结点,令left指针指向它。
(2)移动right指针,使得right指针比left指针超前n个节点。
(3)当right指针比left指针超前n个节点时,两指针开始同时移动,当right指针遍历到链表的末尾时,left指针就恰好处于倒数第n个节点。
【算法描述】
cpp
//双指针迭代法
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* virhead = new ListNode(0, head); //虚拟头结点
ListNode* p_right = head; //右指针
ListNode* p_left = virhead; //左指针
while(n-- && p_right) //让右指针超前左指针n个结点
{
p_right = p_right->next;
}
while(p_right) //两指针同时向前遍历,直到右指针到链表末尾,左指针就恰好处于倒数第n个节点
{
p_right = p_right->next;
p_left = p_left->next;
}
ListNode* p = p_left->next; //删除倒数第n个结点
p_left->next = p->next;
delete p;
return virhead->next;
}
//双指针递归法
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* virhead = new ListNode(0, head); //虚拟头结点
ListNode* p_right = head; //右指针
ListNode* p_left = virhead; //左指针
while(n-- && p_right) //让右指针超前左指针n个结点
{
p_right = p_right->next;
}
remove(p_left, p_right);
return virhead->next;
}
//两指针同时向前遍历,直到右指针到链表末尾,左指针就恰好处于倒数第n个节点
void remove(ListNode* p_left, ListNode* p_right) {
if(!p_right) //删除倒数第n个结点
{
ListNode* p = p_left->next;
p_left->next = p->next;
delete p;
return;
}
return remove(p_left->next, p_right->next); //两指针同时向前遍历
}
【知识点】
在不知道链表或数组等数据类型总长度的情况下求倒数第n个结点的值,并且要求只循环一遍的情况下,可以利用双指针的方法,使得right比left超前n个节点。当right遍历到链表的末尾时,left就恰好处于倒数第n个节点。
合并两个有序链表
【题目描述】
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
【输入输出实例】
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
【算法思路】
方法一:双指针法
当list1和list2都不是空链表时,判断list1和list2哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
首先,我们设定一个哨兵节点head ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个pre指针,我们需要做的是调整它的next指针。然后,我们重复以下过程,直到list1或者list2指向了null :如果list1当前节点的值小于等于list2,我们就把list1当前的节点接在pre节点的后面同时将list1指针往后移一位。否则,我们对list2做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把pre向后移一位。
在循环终止的时候,list1和list2至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
方法二:优先队列
用容量为2的小堆顶优先队列。起始时把两条链表的头结点都放进去,然后出队当前优先队列中最小的(直接出队头元素即可,因为优先队列在插入时已经排好序),挂到新链表上,然后让出队的那个节点的下一个结点(next)入队,再出队当前优先队列中最小的,直到优先队列为空。
【算法描述】
cpp
//双指针法
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
ListNode* head = new ListNode(0); //设立虚拟头结点
ListNode* pre = head; //用于连接新链表
while(list1 && list2)
{
//找两链表中val值小的结点接入到新链表中
if(list1->val <= list2->val)
{
pre->next = list1;
list1 = list1->next;
}
else
{
pre->next = list2;
list2 = list2->next;
}
pre = pre->next; //更新pre指针
}
pre->next = list1 ? list1 : list2; //哪个链表先遍历完,则就把另一个链表剩余结点直接连接到新链表后
return head->next;
}
//优先队列
struct com //重写仿函数
{
bool operator()(ListNode* a, ListNode* b)
{
return a->val > b->val;
}
};
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
priority_queue<ListNode*, vector<ListNode*>, com> q; //创建优先队列
if(list1) //先将两头结点入队
{
q.push(list1);
}
if(list2)
{
q.push(list2);
}
ListNode* head = new ListNode(0); //设立虚拟头结点
ListNode* pre = head; //用于连接新链表
while(!q.empty())
{
ListNode* curr = q.top(); //队首结点是val值最小的
q.pop();
pre->next = curr; //链接新链表
pre = curr; //更新pre指针
if(curr->next) //将该链表上的下一个结点压入
{
q.push(curr->next);
}
}
return head->next;
}
合并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 = [[]]
输出:[]
【算法思路】
使用优先队列:
容器lists中有k个链表,则用容量为K的小堆顶优先队列。起始时把每条链表的头结点都放进去,然后出队当前优先队列中最小的(直接出队头元素即可,因为优先队列在插入时已经排好序),挂上新链表,然后让出队的那个节点的下一个结点(next)入队,再出队当前优先队列中最小的,直到优先队列为空。
【算法描述】
cpp
//优先队列
struct com
{
bool operator()(ListNode* a, ListNode* b) //重写仿函数
{
return a->val > b->val; //小顶堆:val小的在队头
}
};
ListNode* mergeKLists(vector<ListNode*>& lists)
{
priority_queue<ListNode*, vector<ListNode*>, com> q; //创建优先队列
for(auto node : lists) //先将所有头结点入队
{
if(node) //排除空结点
{
q.push(node); //与普通queue不一样的是,优先级队列插入时要排序
}
}
ListNode* head = new ListNode(0); //虚拟头结点
ListNode* pre = head;
while(!q.empty())
{
ListNode* curr = q.top(); //出队的必是队列里val最小的
q.pop();
pre->next = curr; //链接新链表
pre = curr; //更新pre指针
if(curr->next) //将该链表的后续节点插入
{
q.push(curr->next);
}
}
return head->next; //返回新链表头结点
}
//笨办法:将结点数值全部放入一个容器中,排序后重新构造链表
ListNode* mergeKLists(vector<ListNode*>& lists)
{
vector<int> nodeVal; //存放所有链表结点数值
for(auto node : lists) //逐个结点遍历存入nodeVal
{
while(node)
{
nodeVal.push_back(node->val);
node = node->next;
}
}
sort(nodeVal.begin(), nodeVal.end()); //排序
ListNode* head = new ListNode(0); //虚拟头结点
ListNode* pre = head;
for(auto val : nodeVal) //构造一条新链表
{
ListNode* curr = new ListNode(val);
pre->next = curr;
pre = curr;
}
return head->next; //返回新链表头结点
}
【知识点】
优先队列
(1)概念:
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。而在优先队列中,元素被赋予优先级。在插入时自动排序,当访问元素时,具有最高优先级的元素最先出列。
我们可以自定义其中数据的优先级,让优先级高的排在队列前面,优先出队,优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。
(2)定义:priority_queue<Type, Container, Functional>
- Type是队列中存放的数据类型;
- Container是容器类型(Container必须是用数组实现的容器,比如vector, deque等等,但不能用list。STL里面默认用的是vector,一般为vector);
- Functional就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆。
(3)使用:
① int类型
cpp
//greater和less是std实现的两个仿函数
//升序队列------小顶堆:小数在队头
priority_queue<int, vector<int>, greater<int>> q;
//降序队列------大顶堆:大数在队头
priority_queue<int, vector<int>, less<int>> q;
//对于基础类型,只需要传入数据类型即可,默认是大顶堆
priority_queue<int> a;
② pair<int, int>类型
cpp
//对于pair类型的比较,先比较第一个元素,在第一个相等的情况下再比较第二个。
priority_queue<pair<int, int> > a; //默认是大顶堆
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
// 则依次出队时,结果为:
// 2 5
// 1 3
// 1 2
③ 自定义类型
cpp
//方法1
struct tmp1
{
int x;
tmp1(int a) { x = a; }
bool operator<(const tmp1& a) const //运算符重载<
{
return x < a.x; //大顶堆
}
};
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
//则依次出队时,结果为:3 2 1
//方法2------常用
struct tmp2 //重写仿函数
{
bool operator()(tmp1 a, tmp1 b) //注意是tmp1(才有a.x)
{
return a.x > b.x; //小顶堆
}
};
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(c);
f.push(b);
f.push(a);
//则依次出队时,结果为:1 2 3
(4)常用操作:
- top 访问队头元素(普通队列为front)
- empty 队列是否为空
- size 返回队列内元素个数
- push 插入元素到队尾 (并排序)
- emplace 原地构造一个元素并插入队列
- pop 弹出队头元素
- swap 交换内容
两两交换链表中的节点
【题目描述】
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
【输入输出实例】
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
【算法思路】
【算法描述】
cpp
ListNode* swapPairs(ListNode* head)
{
ListNode* virHead = new ListNode(0, head); //虚拟头结点
ListNode* pre = newHead;
ListNode* curr = head;
while(curr && curr->next)
{
pre->next = curr->next; //步骤一
curr->next = curr->next->next; //步骤三
pre->next->next = curr; //步骤二
pre = curr; //更新pre
curr = curr->next; //更新curr
}
return virHead->next;
}
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]
【算法思路】
先算出该链表的所有结点个数,根据结点个数和k算出待翻转的链表组数,则依次遍历这些组。
在遍历各组链表之前,用一个指针preTail来维护上一组的翻转后链表的尾结点,便于连接各组链表(可以建立虚拟头结点,这样的话对于第一组来说,上一组的翻转后尾结点就是虚拟头结点,保证第一组和后面的组都一样)。
遍历各组时,在翻转当前组链表之前,要先用一个指针currHead记录当前组在翻转前的头结点(对于下一组来说,该结点为上一组的翻转后链表的尾结点)。然后利用双指针对当前组链表进行翻转,翻转后用preTail来连接当前组链表,再将preTail更新为currHead。
按照上面步骤来依次循环遍历各组链表,直到到最后一组不够k,则不用翻转,直接将前一组尾结点preTail指向该组头结点即可。
【算法描述】
cpp
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* newHead = new ListNode(0, head); //虚拟头结点
ListNode* pre = newHead; //双指针实现翻转
ListNode* curr = head;
int size = 0; //记录结点个数
while(curr) //求链表的结点个数
{
size++;
curr = curr->next;
}
curr = head;
ListNode* preTail = newHead; //记录前一组已翻转链表的尾结点
for(int i = 0; i < size/k; i++) //遍历待翻转的每组链表
{
ListNode* currHead = curr; //记录当前组在翻转前的头结点
for(int j = 0; j < k; j++) //对当前组链表进行翻转
{
ListNode* temp = curr->next;
curr->next = pre;
pre = curr;
curr = temp;
}
preTail->next = pre; //链接前一组链表和当前组链表
preTail = currHead; //将preTail更新为当前组已翻转链表的尾结点
}
preTail->next = curr; //最后一组不够k,则不用翻转,直接将前一组尾结点指向该组头结点即可
return newHead->next;
}
旋转链表
【题目描述】
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
【输入输出实例】
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4
输出:[2,0,1]
【算法思路】
-
先遍历一次链表,遍历到链表尾结点,求得链表总长度num,同时将链表尾结点与首结点连接起来;
-
再删除无效的旋转次数,例如示例1:head = [1,2,3,4,5], k = 2;如果k = 5,则旋转完还是原链表,相当于没有旋转。k = 6相当于k = 1;k = 7相当于k = 2 ... 所以要对 k 取余:k %= num;
-
再找到原链表的倒数第 k+1个结点,该结点的 next 就是新链表的头结点,再将第 k+1个节点与其 next 结点断开即可。
【算法描述】
cpp
ListNode* rotateRight(ListNode* head, int k) {
if(!head || !head->next || !k) //特殊情况:旋转后还是原链表,直接返回
{
return head;
}
ListNode* curr = head;
int num = 1; //记录链表总结点数
while(curr->next) //找到尾结点,并计算链表总结点数
{
curr = curr->next;
num++;
}
curr->next = head; //将尾结点与首结点链接起来
k %= num; //删除无效的旋转次数
for(int i = 0; i < num - k; i++) //找倒数第k+1个节点
{
curr = curr->next;
}
ListNode* newHead = curr->next; //将倒数第k个节点作为首结点
curr->next = NULL; //将倒数第k+1个节点作为尾结点
return newHead;
}
删除排序链表中的重复元素
【题目描述】
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回已排序的链表 。
【输入输出实例】
示例 1:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
【算法思路】
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。所以从头结点开始遍历链表:
-
如果发现当前节点 curr 与下一个节点 curr->next 对应的 val 相同,那么我们就把 curr->next 节点删除,同时用 curr 链接后面的节点;
-
如果当前 curr 与 curr->next 对应的 val 不相同,那么说明链表中只有一个元素值为 curr->val 的节点,那么我们就可以继续向后遍历链表,即更新
curr = curr->next
指针。
【算法描述】
cpp
ListNode* deleteDuplicates(ListNode* head) {
ListNode* curr = head;
while(curr && curr->next) //遍历链表
{
if(curr->val == curr->next->val) //找到重复数字的结点
{
ListNode* temp = curr->next; //删除重复数字的结点,只保留一个即可
curr->next = temp->next;
delete temp;
continue;
}
curr = curr->next; //未遇到重复结点时,继续往后遍历
}
return head;
}
删除排序链表中的重复元素II
【题目描述】
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回已排序的链表 。
【输入输出实例】
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3]
输出:[2,3]
【算法思路】
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。由于链表的头节点可能会被删除,因此我们需要额外使用一个虚拟头节点。
我们使用两个指针 pre(慢指针)和 curr(快指针)分别指向链表的虚拟头节点和头节点,随后开始对链表进行遍历:
-
如果发现当前节点 curr 与下一个节点 curr->next 对应的 val 相同,那么我们就需要将 curr 以及所有后面拥有相同 val 的链表节点全部删除(删到只剩最后一个相同 val 的节点时,这时该节点为 curr,因为不符合
curr->val == curr->next->val
而退出循环,所以需要单独删除)。删除完最后一个重复结点后,用前一个不重复结点 pre 来链接后面的结点; -
如果当前 curr 与 curr->next 对应的 val 不相同,那么说明链表中只有一个元素值为 curr->val 的节点,那么我们就可以继续向后遍历链表,即更新
pre = curr; curr = curr->next;
两指针。
注意: C++ 代码中要释放被删除的链表节点的空间。
【算法描述】
cpp
ListNode* deleteDuplicates(ListNode* head) {
ListNode* virHead = new ListNode(0, head); //创建虚拟头结点
ListNode* pre = virHead; //慢指针
ListNode* curr = head; //快指针
while(curr && curr->next) //遍历链表
{
if(curr->val == curr->next->val) //找到重复数字的结点
{
while(curr->next && curr->val == curr->next->val) //删除重复数字的结点
{
ListNode* temp = curr;
curr = curr->next;
delete temp;
}
ListNode* temp = curr; //删除重复结点的最后一个
curr = curr->next;
delete temp;
pre->next = curr; //链接后面的结点
continue;
}
pre = curr; //未遇到重复结点时,不断往后更新两指针
curr = curr->next;
}
return virHead->next;
}
分隔链表
【题目描述】
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
【输入输出实例】
示例 1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入:head = [2,1], x = 2
输出:[1,2]
【算法思路】
我们只需维护两个链表 small 和 large 即可,small 链表按顺序存储所有小于 x 的节点,large 链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,我们只要将 small 链表尾节点指向 large 链表的头节点即能完成对链表的分隔。
-
为了处理头节点的边界条件,我们设 smallHead 和 largeHead 分别为两个链表的虚拟头节点。随后,从前往后遍历链表,判断当前链表的节点值是否小于 x,如果小于就将该节点链接到 small 链表后,否则链接到 large 链表后;
-
遍历结束后,我们将 large 链表的尾部 next 指针置空,这是因为尾节点复用的是原链表的节点,而其 next 指针可能指向一个小于 x 的节点,我们需要切断这个引用。同时将 small 链表的尾部 next 指针指向 largeHead->next(即真正意义上的 large 链表的头节点)。最后返回 smallHead->next 指针即为我们要求的答案。
【算法描述】
cpp
//利用两个链表虚拟头结点,分别构造所有结点val < x 或 val ≥ x的两条链表,再将两条链表链接起来
ListNode* partition(ListNode* head, int x) {
ListNode* smallHead = new ListNode(0); //val < x的结点构成链表的虚拟头结点
ListNode* currSmall = smallHead;
ListNode* largeHead = new ListNode(0); //val ≥ x的结点构成链表的虚拟头结点
ListNode* currLarge = largeHead;
while(head) //遍历链表
{
if(head->val < x) //构造所有结点 val < x 的链表
{
currSmall->next = head;
currSmall = currSmall->next;
}
else //构造所有结点 val ≥ x 的链表
{
currLarge->next = head;
currLarge = currLarge->next;
}
head = head->next;
}
currLarge->next = NULL; //val ≥ x的链表尾部next置NULL
currSmall->next = largeHead->next; //val ≥ x 的链表拼接在 val < x 的链表后面
return smallHead->next;
}
反转链表
【题目描述】
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
【输入输出实例】
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
【算法思路】
- 定义两个指针: pre 和 cur ;pre 在前 cur 在后。
- 每次让 pre 的 next 指向 cur ,实现一次局部反转。
- 局部反转完成之后,pre 和 cur 同时往前移动一个位置。
- 循环上述过程,直至 pre 到达链表尾部
【算法描述】
cpp
ListNode* reverseList(ListNode* head) {
ListNode* cur = NULL, *pre = head;
while (pre != NULL) {
ListNode* t = pre->next;
pre->next = cur;
cur = pre;
pre = t;
}
return cur;
}
反转链表II
【题目描述】
给你单链表的头指针head和两个整数 left和right (left <= right)。请你反转从位置left到位置right的链表节点,返回反转后的链表。
【输入输出实例】
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
【算法思路】
我们以下图中黄色区域的链表反转为例。
使用206、反转链表的解法,反转left到right部分以后,再拼接起来。我们还需要提前记录left结点和其前一个节点pre。如图所示:
算法步骤:
- 第 1 步:先将待反转的区域反转;
- 第 2 步:把pre的next指针指向反转以后的链表头节点,把反转以后的链表的尾节点(即提前记录的left结点)的next指针指向succ。
【算法描述】
cpp
//穿针引线:反转left到right的链表结点,再更新left前一个结点的链接和更新right后一个结点的链接
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* newHead = new ListNode(0, head); //虚拟头结点
ListNode* curr = newHead; //当前结点
ListNode* leftNode = head; //left位置的结点
ListNode* leftPreNode = newHead; //left位置的前一个结点
//找left位置的结点和前一个结点
for(int i = 0; i < left - 1; i++)
{
curr = curr->next;
}
leftPreNode = curr;
leftNode = curr->next;
//反转 left 到 right 的链表结点
ListNode* pre = curr; //反转时要记录前一个结点
curr = curr->next;
for(int i = left; i <= right; i++)
{
ListNode* temp = curr->next; //反转时要记录后一个结点
curr->next = pre;
pre = curr; //更新pre
curr = temp; //更新curr
}
leftPreNode->next = pre; //更新left前一个结点的链接
leftNode->next = curr; //更新right后一个结点的链接
return newHead->next;
}
恭喜你全部读完啦!古人云:温故而知新。赶紧收藏关注起来,用之前再翻一翻吧~
📣推荐阅读
C/C++后端开发面试总结:点击进入 后端开发面经 关注走一波
C++重点知识:点击进入 C++重点知识 关注走一波
力扣(leetcode)题目分类:点击进入 leetcode题目分类 关注走一波