题目: 给你一个链表,每k个节点一组进行翻转,请你返回翻转后的链表;
k是一个正整数,它的值小于或等于链表的长度;
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序;
给你这个链表:1->2->3->4->5
当 k=2 时,应当返回: 2->1->4->3->5
当 k=3 时,应当返回: 3->2->1->4->5
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换
1.栈:
[1]思路: 遍历链表,每遍历到一个添加到栈中,
如果添加的计数不足k个则直接指向剩余的,如果计数为k则从栈中取节点添加到反转的链表中;
[2]实现:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 定义一个双端队列对象作为"栈"
Deque<ListNode> stack = new ArrayDeque<>();
// 定义一个"假"节点,用来返回反转后的链表
ListNode dummy = new ListNode(0);
// 定义一个用来遍历"假"节点的节点
ListNode p = dummy;
// 遍历给定的链表
while (true){
// 定义一个计数变量
int count = 0;
// 定义一个用来遍历给定链表的节点
ListNode temp = head;
// 遍历k个节点,添加到"栈"中
while (temp != null && count < 3){
stack.add(temp);
temp = temp.next;
count++;
}
// 判断是否够k个节点,不够k个则直接接上每次遍历的头结点
if(count != k){
p.next = head;
break;
}
// 如果stack中不为空(即满k个节点)则需要反转
while (!stack.isEmpty()){
p.next = stack.pollLast();
p = p.next;
}
// 节点后移(遍历的和添加的都需要)
p.next = temp;
head = temp;
}
return dummy.next;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
public int getVal() { return val; }
public void setVal(int val) { this.val = val; }
}
[3]复杂度分析:
(1)时间复杂度: O(n)
(2)空间复杂度: O(k),k为每组节点的个数;
2.尾插法:
[1]思路:
(1)创建一个节点,其下个节点指向给定节点,再定义两个节点引用指向创建的节点;
(2)遍历给定节点,每遍历完一组(k个),pre指向一组节点中头节点的前一个节点,tail指向尾节点;
(3)判断tail是否为空,为空说明遍历的个数不足k个,无需反转,直接返回;
(4)否则使用尾插法:每次取当前组中的第一个插入到尾部,知道pre.next等于tail(相等说明该组反转完成);
(5)将pre和tail重置位置;
[2]实现:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 反转链表的头结点
ListNode dummy = new ListNode(0);
// 反转链表的头结点指向给定节点
dummy.next = head;
// 定义两个节点指向一组节点中头节点的前一个节点和尾节点
ListNode pre = dummy;
ListNode tail = dummy;
// 遍历给定节点
while (true) {
// 计数变量
int count = 0;
// 移动tail到当前组的尾节点
while (tail != null && count != k) {
count++;
tail = tail.next;
}
// 如果为null则说明不满k个,无需反转,直接退出循环
if (tail == null) {break;}
// 先确定一组里的头结点,用来确定下一组的开始位置
ListNode head1 = pre.next;
// 不断的取一组里的头结点插入到尾节点之后
while (pre.next != tail) {
// 前两步的作用是取出当前遍历到的节点
ListNode cur = pre.next;
pre.next = cur.next;
// 这两步是尾插法的精髓,请仔细想明白
cur.next = tail.next;
tail.next = cur;
}
// 重置到下一组的头结点的前一个节点和尾节点
pre = head1;
tail = head1;
}
return dummy.next;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
public int getVal() { return val; }
public void setVal(int val) { this.val = val; }
}
[3]复杂度分析:
(1)时间复杂度: O(n)
(2)空间复杂度: O(1)
3.递归:
[1]思路: 参考代码注释
[2]实现:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 使用一个引用指向给定的节点,用于遍历
ListNode cur = head;
// 定义一个计数变量
int count = 0;
// 遍历给定的节点获取一组
while (cur != null && count != k) {
cur = cur.next;
count++;
}
// 如果count等于k,说明够一组
if (count == k) {
// 获取下一组的头结点(不断递归,不断卡在这里,直至不满k个)
cur = reverseKGroup(cur, k);
// 正式开始反转(这里比较难理解,可通过打断点看每步执行后的结果)
while (count != 0) {
count--;
// [1]用临时节点保存当前组的第二个节点
ListNode tmp = head.next;
// [2]将下一组的头结点作为当前组头结点的下一个节点
head.next = cur;
// [2]使用cur节点保存每次遍历后的反转链表的头结点
cur = head;
// [1] 每遍历一次去掉一个头结点,该头结点放在cur的头部
head = tmp;
}
// 把反转完的链表的头结点赋给原链表头
head = cur;
}
return head;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
public int getVal() { return val; }
public void setVal(int val) { this.val = val; }
}
[3]复杂度分析:
(1)时间复杂度: O(n)
(2)空间复杂度: O(1)