将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
数据范围: \ 0 \le n \le 2000 0≤n≤2000 , 1 \le k \le 20001≤k≤2000 ,链表中每个元素都满足 0 \le val \le 10000≤val≤1000
要求空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如:
给定的链表是 1\to2\to3\to4\to51→2→3→4→5
对于 k = 2k=2 , 你应该返回 2\to 1\to 4\to 3\to 52→1→4→3→5
对于 k = 3k=3 , 你应该返回 3\to2 \to1 \to 4\to 53→2→1→4→5
题解:
这解看代码吧!
首先,我们要实现一个reverse函数反转一个区间之内的元素。在此之前我们再简化一下,在给定头节点的情况下对链表进行反转。
// 反转以 a 为头结点的链表
//这里是连带着头结点一起翻转,可以说成是一个区间的结点的翻转;
ListNode reverse(ListNode a) {
ListNode pre, cur, nxt;
pre = null; cur = a; nxt = a;
while (cur != null) {
nxt = cur.next;
// 逐个结点反转
cur.next = pre;
// 更新指针位置
pre = cur;
cur = nxt;
}
// 返回反转后的头结点
return pre;
}
反转以a为头结点的链表其实就是反转a到null之间的结点,那么反转a到b之间的结点就只需要吧null改为b是不是就可以实现了?下面给出反转a到b之间节点的代码:
/** 反转区间 [a, b) 的元素,注意是左闭右开 */
ListNode reverse(ListNode a, ListNode b) {
ListNode pre, cur, nxt;
pre = null; cur = a; nxt = a;
// while 终止的条件改一下就行了
while (cur != b) {
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
// 返回反转后的头结点
return pre;
}
现在我们迭代实现了反转部分链表的功能,那么只需要遍历种反转链表不就是可以了么,下面是完整代码:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
auto node=head;
for (int i=0;i<k;i++) {
if (!node) return head;
node = node->next;
}
auto res = reverse(head, node); // 翻转长度是k的链表
head->next = reverseKGroup(node, k); // 递归处理下一个长度是k的链表
return res; //返回头指针
}
private:
//reverse函数实现反转a到b之间的结点
ListNode* reverse(ListNode* left, ListNode* right) {
auto pre=right;//前面都是null,这里却改成了right,应为 right翻转后是尾结点,
//这样链表就连在了一起。right是下一个链表的头结点(翻转开头的结点);
while (left!=right) {
auto node=left->next;
left->next=pre;
pre = left;
left = node;
}
return pre;
}
};
时间复杂度:O(n)
空间复杂度:O(1) 没有使用额外空间
优缺点:时间复杂度低,但是思考过程难 不容易想到
小结:这里的翻转,一般有头结点我们一般容易想到利用头插法来翻转链表,但头插法翻转不会连带着头结点一起翻转,这里其实是利用里一个假的头结点的概念,所以翻转后假头结点也会一起翻转。再者这种转链表的方法还挺特别:
它是直接在直接记录了翻转后链表的尾结点的指向后一链表的头结点,从第一个结点开始,将原链表一个个的反向指向它的前一个结点,而第一个结点直接指向后一链表的头结点,这样就使链表连接了起来,而又记录了个个链表部分的头结点,是的递归算法得以实现。