题目地址:力扣
这道题目思路其实不算太难,只是细节的地方需要处理的比较多,我们如果采用哨兵节点就可以很容易解决一些需要if判断的地方。比如翻转后的子链需要连接到原链最后一个节点的next,但是如果是第一个翻转的子链就没有上一个节点,因此这里本来需要进行特殊的if判断,我们引入哨兵节点之后就可以统一化处理。还需要注意的地方就是,这道题如果最后剩余的元素不到k,那么最后就不进行翻转,因此我们需要先探路,探路后发现长度满足条件再进行翻转。
解法1:三指针法进行链表翻转
class Solution {
public:
void reverseList(ListNode* node, int k)
{
// 三个指针分别对应前,中,后
ListNode* pre = nullptr;
ListNode* cur = node;
ListNode* nex = cur->next;
// 这里就是翻转链表的基本操作,只是用k来判断要翻转多少个节点
while (k > 0)
{
nex = cur->next;
cur->next = pre;
pre = cur;
cur = nex;
--k;
}
// node存储着原先的头节点(翻转后的尾节点),而nex记录下一组未翻转链表的头节点
// 因此需要把现在的尾节点指向下一组未翻转链表的头节点
node->next = nex;
}
ListNode* reverseKGroup(ListNode* head, int k) {
// 哨兵节点
ListNode* sentry = new ListNode(-1, head);
// 记录上一组链表的尾节点
ListNode* prev_tail = sentry;
// 用于探路的指针
ListNode* pioneernode = head;
// 只要下一组待翻转节点的头不为空
while (head != nullptr)
{
int pioneer = k;
// 确保链表后面有k-1个节点供反转
while (pioneernode != nullptr && pioneer > 1)
{
--pioneer;
pioneernode = pioneernode->next;
}
// 这里探路节点要么为空,说明后面的长度不满足了,那么就跳出循环
if (pioneernode == nullptr)
break;
// 满足翻转条件的话,探路节点等于本组待翻转链表的尾节点。
// 把头节点作为参数传入翻转函数进行翻转
reverseList(head, k);
// 翻转完毕后,探路节点变为链表的头节点,head变为链表的尾节点
// 将当前翻转完后的链表接在前一个节点的后面,并把前一个节点设为当前链表的尾节点
prev_tail->next = pioneernode;
prev_tail = head;
// head和探路节点都指向下一个待翻转链表的头节点
head = head->next;
pioneernode = head;
}
// 最后返回哨兵节点的next即可
return sentry->next;
}
};
解法2:尾插法进行链表翻转
尾插法则比上一种方法要更加简洁,其思想是找到待翻转部分的头和尾,将从头开始一个个把节点接在尾的后面,这样的好处就是翻转完之后当前部分和下一部分还是非常自然的接在一起的。但是还是需要处理当前翻转部分和上一部分尾节点的关系。
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
// 哨兵节点
ListNode* sentry = new ListNode(-1, head);
// 记录上一部分的尾节点
ListNode* prev_tail = sentry;
// tail用于表示尾节点,初始化为头节点
ListNode* tail = head;
// 工作节点,用于实现尾插法
ListNode* work;
// 只要头节点不为空就继续循环
while (head != nullptr)
{
// 将工作节点设为头节点,cnt用于计数
work = head;
int cnt = k;
// 确保链表后有k-1个节点供反转,tail也充当探路节点,探路完毕后指向待翻转链表尾
while (tail != nullptr && cnt > 1)
{
--cnt;
tail = tail->next;
}
// 如果发现不满足翻转条件则跳出循环
if (tail == nullptr)
break;
// 只要工作节点没指向尾节点,就使用尾插法从头节点一个一个把节点取出,插入尾节点后面
while (work != tail)
{
ListNode* tmp = work;
work = work->next;
tmp->next = tail->next;
tail->next = tmp;
}
// 因为tail指向原链表尾节点(翻转后的头节点),head指向翻转后的尾节点
// 将上一部分尾和这一部分头连起来,并将上一部分尾指向这一部分尾()
prev_tail->next = tail;
prev_tail = head;
// tail和head都移动到要翻转下一部分的头节点
tail = head->next;
head = tail;
}
// 返回哨兵节点的next
return sentry->next;
}
};
Accepted
- 62/62 cases passed (16 ms)
- Your runtime beats 45.89 % of cpp submissions
- Your memory usage beats 83.69 % of cpp submissions (11.1 MB)