K 个一组翻转链表
难度:
标签:
题目描述
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 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]
提示:
- 链表中的节点数目为
n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
进阶:你可以设计一个只用 O(1)
额外内存空间的算法解决此问题吗?
代码结果
运行时间: 52 ms, 内存: 16 MB
/*
题目思路:
1. 使用Java Stream API不能直接操作链表节点,因此这里的实现会将链表转换为数组。
2. 每k个元素进行一次翻转,最后将数组转换回链表。
3. 返回新的链表头。
*/
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 将链表转换为ArrayList
List<Integer> values = listToArrayList(head);
// 分段翻转
for (int i = 0; i < values.size(); i += k) {
if (i + k <= values.size()) {
List<Integer> sublist = values.subList(i, i + k);
java.util.Collections.reverse(sublist);
}
}
// 将ArrayList转换回链表
return arrayListToList(values);
}
private List<Integer> listToArrayList(ListNode head) {
List<Integer> list = new java.util.ArrayList<>();
while (head != null) {
list.add(head.val);
head = head.next;
}
return list;
}
private ListNode arrayListToList(List<Integer> values) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
for (int val : values) {
current.next = new ListNode(val);
current = current.next;
}
return dummy.next;
}
}
解释
方法:
这个题解使用了递归的思路。首先判断链表长度是否小于k,如果是则直接返回原链表。否则,找到第k个节点,作为当前要反转的子链表的尾节点。然后调用reverse函数反转从head到第k个节点这一段的子链表。反转完后,head成为子链表的尾节点,递归处理之后的节点,将其作为head的next。最后返回反转后的链表的头节点,即反转前的第k个节点。
时间复杂度:
O(n)
空间复杂度:
O(n)
代码细节讲解
🦆
在处理非k整数倍的剩余节点时,题解中提到`如果节点总数不是k的整数倍,那么请将最后剩余的节点保持原有顺序`。这一点是如何在代码中实现的,特别是在寻找子链表的尾节点时的逻辑判断中?
▷🦆
题解中提到使用递归方法,递归深度为n/k。在实际应用中,递归深度过大可能导致栈溢出。对于最大节点数5000,这种递归方法是否安全,或者是否有必要考虑使用迭代法代替递归法来避免栈溢出问题?
▷🦆
您的解法中定义了一个内部函数`reverse`用于反转链表的一部分。这个函数的参数是头节点h和尾节点的下一个节点t,能否具体解释这种参数选择对整个链表反转逻辑的影响?
▷🦆
对于反转操作中的`cur.next = pre`步骤,这种修改链表指针的操作在并发环境下是否安全?如果有多个线程同时操作链表,是否需要考虑加锁来保证数据一致性?
▷