给你链表的头节点 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 <= 50000 <= Node.val <= 1000
进阶: 你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?
解法一:
这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,一个是用来翻转的,以题目中给的例子来看,对于给定链表 1->2->3->4->5,一般在处理链表问题时,大多时候都会在开头再加一个 dummy node,因为翻转链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的 dummy node,加入 dummy node 后的链表变为 -1->1->2->3->4->5,如果k为3的话,目标是将 1,2,3 翻转一下,那么需要一些指针,pre 和 next 分别指向要翻转的链表的前后的位置,然后翻转后 pre 的位置更新到如下新的位置:
以此类推,只要 cur 走过k个节点,那么 next 就是 cur->next,就可以调用翻转函数来进行局部翻转了,注意翻转之后新的 cur 和 pre 的位置都不同了,那么翻转之后,cur 应该更新为 pre->next,而如果不需要翻转的话,cur 更新为 cur->next,代码如下所示:
class Solution {
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head?.next == null || k == 1) {
return head
}
val dummy = ListNode(-1)
var pre: ListNode? = dummy
var cur: ListNode? = head
dummy.next = head
var i = 1
while (cur != null) {
if (i % k == 0) {
pre = reverseOneGroup(pre, cur.next)
cur = pre?.next
} else {
cur = cur.next
}
i++
}
return dummy.next
}
private fun reverseOneGroup(pre: ListNode?, tail: ListNode?): ListNode? {
val last: ListNode? = pre?.next
var cur: ListNode? = last?.next
while (cur != tail) {
last?.next = cur?.next
cur?.next = pre?.next
pre?.next = cur
cur = last?.next
}
return last
}
}
复杂度分析
时间复杂度:O(n),其中 n 为链表的长度。head 指针会在 O(⌊ k n ⌋) 个节点上停留,每次停留需要进行一次 O(k) 的翻转操作。
空间复杂度:O(1),我们只需要建立常数个变量。
解法二:
我们也可以在一个函数中完成,首先遍历整个链表,统计出链表的长度,然后如果长度大于等于k,交换节点,当 k=2 时,每段只需要交换一次,当 k=3 时,每段需要交换2次,所以i从1开始循环,注意交换一段后更新 pre 指针,然后 num 自减k,直到 num<k 时循环结束,参见代码如下:
class Solution {
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
val dummy = ListNode(-1)
var pre: ListNode? = dummy
var cur: ListNode? = pre
dummy.next = head
var num = 0
while (cur != null) {
num++
cur = cur.next
}
while (num > k) {
cur = pre?.next
for (index in 1 until k) {
val tempNode = cur?.next
cur?.next = tempNode?.next
tempNode?.next = pre?.next
pre?.next = tempNode
}
pre = cur
num -= k
}
return dummy.next
}
}
解法三:
我们也可以使用递归来做,用 head 记录每段的开始位置,cur 记录结束位置的下一个节点,然后调用 reverse 函数来将这段翻转,然后得到一个 new_head,原来的 head 就变成了末尾,这时候后面接上递归调用下一段得到的新节点,返回 new_head 即可,参见代码如下:
class Solution {
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
var cur: ListNode? = head
for (index in 1..k) {
if (cur == null) {
return head
} else {
cur = cur.next
}
}
val newHead = reverse(head, cur)
head?.next = reverseKGroup(cur, k)
return newHead
}
private fun reverse(head: ListNode?, tail: ListNode?): ListNode? {
var pre: ListNode? = null
var tempHead = head
while (tempHead != tail) {
val t = tempHead?.next
tempHead?.next = pre
pre = tempHead
tempHead = t
}
return pre
}
}