
在做这道题之前,先来看一个 206.翻转链表 的题做下铺垫。

解析:
使用迭代法来遍历链表。下图展示了整个链表翻转的详细过程。基本思想就是:在遍历链表时,将当前节点 curr 的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须提前存储其前一个节点,又因为首节点前没有节点,所以要设置虚拟头节点 dummy 。在更改引用之前,还需要存储后一个节点 nextNode 。最后返回新的头节点 dummy 即可。
简单来讲四步:
步骤1:保存 curr 的下一个节点,即 nextNode := curr.Next
步骤2:curr 的下一个节点指向 dummy 虚拟头节点,即 curr.Next = dummy
步骤3:dummy 指向下一个节点,也就是当前 curr 的位置,即 dummy = curr
步骤4:curr 指向下一个节点,也就是步骤 1 中保存的节点,即 curr = nextNode

代码:
type ListNode struct {
Val int
Next *ListNode
}
func reverseList(head *ListNode) *ListNode {
var dummy *ListNode
curr := head
for curr != nil {
nextNode := curr.Next
curr.Next = dummy
dummy = curr
curr = nextNode
}
return dummy
}
复杂度分析:
时间复杂度:O(n),其中 n 是链表的长度。
空间复杂度:O(1)。
解决了翻转整个链表,接下来再来看翻转 K 个一组链表。
解析:
将链表节点按照 k 个一组进行分组,所以可以使用一个 head 依次指向每组的头节点。这个指针每次向前移动 k 步,直至链表结尾。对于每个分组,判断它的长度是否大于等于 k 。若是,就翻转这部分子链表,否则bu’x将链表节点按照 k 个一组进行分组,所以可以使用一个 head 依次指向每组的头节点。这个指针每次向前移动 k 步,直至链表结尾。对于每个分组,判断它的长度是否大于等于 k 。若是,就翻转这部分子链表,否则不需要翻转。
翻转一个子链表的问题就回到了上面的力扣 206.反转链表。但是不同的是,在 206.反转链表中输入和返回的都是链表的头节点,这里做子链表翻转不仅需要传入链表头节点,还要传入链表尾节点,返回同样也包括头节点和尾节点。
将翻转后的子链表的头节点与上一个子链表尾节点链接,以及子链表的尾节点与下一个子链表连接。
type ListNode struct {
Val int
Next *ListNode
}
func reverseKGroup(head *ListNode, k int) *ListNode {
dummyNode := &ListNode{Next: head}
dummy := dummyNode
for head != nil {
tail := dummy
for i := 0; i < k; i++ {
tail = tail.Next
if tail == nil {
return dummyNode.Next
}
}
// 保存当前子链表的尾节点的Next
nextNode := tail.Next
// 翻转子链表
head, tail = reverse(head, tail)
// 连接翻转后的子链表,虚拟头节点的Next为翻转后的head,tail的Next为 nextNode
dummy.Next = head
tail.Next = nextNode
// 下一个子链表
dummy = tail //下一个子链表的虚拟头节点是上一个子链表的尾节点
head = tail.Next //下一个子链表的头节点是上一个子链表的尾节点的Next
}
return dummyNode.Next
}
// 翻转子链表
func reverse(head, tail *ListNode) (*ListNode, *ListNode) {
prev := tail.Next
curr := head
for prev != tail {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
return tail, head
}
复杂度分析:
时间复杂度:O(n),其中 n 是链表的长度。
空间复杂度:O(1)。

No responses yet