大家好,我是怒码少年小码。
很多人是不是写了这么多天的链表都烦了哈哈,今天这篇结束之后链表部分我们就暂告一段落,即将开启新的旅程😉!
K个一组反转
LeetCode25:给你一个链表,每k个结点一组进行反转,然后请返回反转后的链表。k是一个正整数,它小于等于链表的长度。如果总结点个数不是k的整数倍,那么剩余的结点请保持原有顺序。
PS:你不能只是😁单纯的改变结点的值,而是要进行结点交换。
示例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]
不难,但需要思考🤔。开始之前我们先把一些通用的东西准备好。
定义结点:
struct LinkNode {
int elem;
LinkNode* next;
};
测试的时候肯定会需要创建链表和输出打印的,我们干脆直接抽象封装起来好了:
//打印的函数
void printList(LinkNode* head) {
LinkNode* cur = head;
while (cur != nullptr) {
cout << cur->elem <<",";
cur = cur->next;
}
//打印完一个链表之后换行
cout << endl;
}
链表结点一个个创建和初始化太麻烦了,用数组保存结点的值,然后再接起来这样可以增强代码的复用性:
LinkNode* initList(int arr[], int len) {
//创建头结点
LinkNode* head = new LinkNode();
head->elem = arr[0];
head->next = nullptr;
//用于遍历的指针
LinkNode* cur = head;
//int len = sizeof(arr) / sizeof(arr[0]);
//拼接链表
for (int i = 1; i < len; i++) {
LinkNode* p = new LinkNode();
p->elem = arr[i];
p->next = nullptr;
cur->next = p;
cur = p;
}
//打印链表
printList(head);
return head;
}
不知道有没有小伙伴有和我一样的疑问:为什么不直接在函数中判断数组的大小🤔?就像我代码中注释的那行int len = sizeof(arr) / sizeof(arr[0]);
一样,这样不是会少传一个值吗?
我试过了,那样做是不行滴~⭐在C++中,传递给函数的数组参数实际上会退化为指针。所以在函数内部,sizeof(arr)
返回的是指针的大小,而不是数组的大小。因此,在函数内部,sizeof(arr) / sizeof(arr[0])
的结果是无效的,不能用于计算数组的大小。
以上写的这些代码都是很常用的,大家保存好,以后直接CV就行了✌️。下面,正片开始(题目要是忘了再往上翻翻看看)。
1.头插法
什么是头插法请看我之前的文章:
这题其实我们很容易就想到要把链表分段:已经反转好的、正在反转好的和未反转的。还要统计一下一共有多少个结点,这是为了知道要反转多少组k个结点int n = len / k;
。
整数除法 /
在C++中是向下取整的。也就是说,无论除法的结果是否有小数部分,都会截断小数部分,只保留整数部分。这样就正好符合了题目要求的不是k的整数倍就保留这一点。
LinkNode* reverseKGroup(LinkNode* head, int k) {
LinkNode* dummy = new LinkNode();
dummy->next = head;
LinkNode* cur = head;
int len = 0;
//统计一共有多少个结点
while (cur != nullptr) {
len++;
cur = cur->next;
}
int n = len / k;
LinkNode* pre = dummy;
cur = head;
for (int i = 0; i < n; i++) {
for (int j = 0; j < k - 1; j++) {
LinkNode* next = cur->next;
cur->next = cur->next->next;
next->next = pre->next;
pre->next = next;
}
pre = cur;
cur = cur->next;
}
printList(dummy->next);
return dummy->next;
}
有n组,每组有k个需要反转,所以会用到双重循环遍历链表。
2.穿针引线法
这种方法的思路还是把整个链表分成三部分:已经反转好的、正在反转的和未反转的。只不过与上面不同的是:直接把正在反转的部分拆下来,然后单独当成一个新链表,单独反转完后在安回去,如此循环往复。
所以😎我们要先定义一个专门用于反转链表的函数:
LinkNode* reverse(LinkNode* head) {
LinkNode* pre = nullptr;
LinkNode* curr = head;
while (curr != nullptr) {
LinkNode* next = curr->next;
curr->next = pre;
pre = curr;
curr = next;
}
return pre;
}
要单独反转就要记录拆下来的新链表的新头指针start
、新链表的尾指针end
;要把反转后的链表接回去就要保存拆下新链表头结点的前一个结点的地址pre
,新链表尾结点的后一个结点的地址next
。
LinkNode* reverseKGroup02(LinkNode* head, int k) {
LinkNode* dummy = new LinkNode();
dummy->next = head;
LinkNode* pre = dummy;
LinkNode* end = dummy;
while (end->next != nullptr) {
//找到要处理的区间的末尾
int i;
for (i = 0; i < k && end != nullptr; i++) {
end = end->next;
}
//如果链表长度不是k的整数倍
if (end == nullptr) {
break;
}
//将要处理的区间裁剪下来
LinkNode* start = pre->next;
LinkNode* next = end->next;
end->next = nullptr;
//执行反转,反转后接回原链表
pre->next = reverse(start);
start->next = next;
//调整指针,为下一组做准备
pre = start;
end = pre;
}
//打印结果
printList(dummy->next);
return dummy->next;
}
其中反转过后,reverse(start)
返回的是反转过后链表的新头指针,为了接回原链表应该接在pre->next
上;反转过后的链表的start
是尾指针,为了接回去应该要接在原链表断开的下一个结点之前😁,所以是start->next = next;
。如下图:
然后调整各个指针的位置,为下一次准备,一直到end->next == nullptr
。如果链表长度不是k的整数倍⭐,在最后一个新链表的长度肯定不满k,那么在查找新链表的尾结点时end = nullptr
,会被程序判断直接跳出循环,不再做后面的反转操作。
END
这个系列不知不觉也写了五篇了😊,果然一写代码和文章时间就是过的快,后续还会持续更新!