LeetCode 25. K 个一组翻转链表

401 阅读3分钟

leetcode.cn/problems/re…

www.cnblogs.com/grandyang/p…

给你链表的头节点 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) 额外内存空间的算法解决此问题吗?

解法一:

这道题让我们以每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
    }
}