持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
题目
描述
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样 你不能更改节点中的值,只能更改节点本身。
数据范围: 0≤n≤2000 ,1≤k≤2000 ,链表中每个元素都满足 0≤val≤1000 要求空间复杂度 O(1),时间复杂度 O(n)
例如:
给定的链表是1→2→3→4→5
对于 k = 2 , 你应该返回2→1→4→3→5
对于 k = 3 , 你应该返回 3→2→1→4→5
示例1
输入:
{1,2,3,4,5},2
返回值:
{2,1,4,3,5}
示例2
输入:
{},1
返回值:
{}
分析
考虑特殊情况
在进行问题分析过程前,我们先考虑一下特殊情况,那么在这道问题中,有哪些情况是特殊的呢?
不难发现,如下两种情况:
- 空链表,即头指针为NULL,这种情况我们直接返回头指针即可;
- 链表只有一个节点,即pHead->next为NULL,这种情况我们也直接返回头指针即可。
- 如果k<=1,说明并不需要进行链表反转,直接返回头指针即可
考虑一般情况
这里我们看一下问题的一般形式,也即是对中间的每一组链表进行逆序,这个的方法其实在我们之前的问题中已经出现过了链表内指定区间反转 ,所以这里我们就将问题做一下拆解:
- 找出当前需要进行逆序的子链表,确定该子链表的pPre和pEnd;
- 对pPre到pEnd中间的节点进行区间逆序算法;
- 更新pPre和pEnd到下一个子链表,直到无法找到满足条件的子链表。
注意,这里我们也同样使用一个虚拟node来指向我们的头结点,避免对头结点做判断导致的逻辑复杂。
代码示例
注意:如下的代码示例中
- pReturn是我们的返回节点,它指向头结点;
- pPre是每一个子链表的头结点的前驱节点,开始时初始化为pReturn,完成子链表逆序之后被更新为pCur的值,在子链表逆序过程中不发生改变;
- pCur是子链表中当前指向的节点,在逆序完成后(即pNext节点被插入pPre后面之后)指向下一个子链表的第一个元素,也即是下一个子链表头节点的前驱节点;
- pNext是pCur的下一个元素,如果不等于pEnd,那就需要逆序插入到pPre后面;
- pEnd是子链表的尾节点。
还有一点,如果我们在寻找pEnd节点的过程中遍历到了链表末尾,那说明最后的这一组不需要做子链表逆序,直接返回pReturn->next即可。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
if (nullptr == head || nullptr == head->next || k <= 1) {
return head;
}
ListNode* pReturn = static_cast<ListNode*>(malloc(sizeof(ListNode)));
pReturn->val = -1;
pReturn->next = head;
ListNode* pPre = nullptr;
ListNode* pCur = nullptr;
ListNode* pNext = nullptr;
ListNode* pEnd = nullptr;
pPre = pReturn;
while (nullptr != pPre) {
pEnd = pPre;
for (int i = 0; i < k; ++i) {
pEnd = pEnd->next;
if (nullptr == pEnd) {
// no need reverse for this seg list
return pReturn->next;
}
}
// reverse sublist until to the pEnd
pCur = pPre->next;
while (pNext != pEnd) {
pNext = pCur->next;
pCur->next = pNext->next;
pNext->next = pPre->next;
pPre->next = pNext;
}
// assign the pPre
pPre = pCur;
}
return pReturn->next;
}
};