LeetCode k 个为一组进行翻转

85 阅读4分钟

LeetCode k 个为一组进行翻转

题目需要将单链表以 k 个为一组进行翻转的情况。这个问题在实际的编程中也很常见,解决这个问题可以提高我们的链表操作技能。

本文将分享如何实现单链表的 k 个一组翻转以及相关的知识点,带给大家更深入、更系统的链表操作理解。

题目描述

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例

输入:1->2->3->4->5

当 k = 2 时,输出:2->1->4->3->5

当 k = 3 时,输出:3->2->1->4->5

解题思路

这个问题可以转化为把每 k 个结点分为一组,然后对每一组进行翻转。

做题常用的方法是:先定义一个哑结点,然后将其 next 指针指向头结点。这样可以有效减少很多边界情况的判断,同时对于头结点的操作可以和对于其他结点的操作一致。

我们可以每次找到待反转的 k 个结点的位置,然后反转这 k 个结点。但是这个过程中要注意的是边界条件的判断,也就是当翻转完最后一组结点的时候,需要把剩余的结点串起来。通过哑结点,这个过程可以简化很多。

在具体实现过程中我们可以定义如下几个变量:

  • pre:每一组开始前的结点,也就是上一组翻转后的结点。
  • end:每一组结束后的结点,也就是这一组翻转后的最后一个结点。
  • start:每一组开始前的结点,也就是这一组翻转后的第一个结点。
  • next:下一组开始前的结点,也就是这一组翻转后的最后一个结点的下一个结点。

具体的操作步骤:

  1. 首先判断链表是否为空或者 k 是否为 1,如果是则直接返回头结点。

  2. 定义虚拟的头结点dummyHead,将其 next 指针指向链表的头结点head。

  3. 定义 pre、start、end 的初始位置,即 pre = dummyHead,start 和 end 都指向 head。

  4. 根据图片中描述的顺序,翻转 start 到 end 之间的结点。

  5. 将翻转完的这组结点连接起来,操作如下:

    • pre 的 next 指针指向 end,即上一组翻转后的结点指向这一组翻转后的最后一个结点。
    • start 结点指向下一组翻转的开始结点,即 end 的下一个结点。
    • 完成连接后,将 pre 指向 start 即可,这样就完成了一组的翻转操作。
  6. 处理剩余的部分,将剩下的未处理的结点连接到最终链表的末尾。

  7. 返回 dummyHead 的下一个结点即为结果。

代码实现

下面是用 Java 语言实现的代码,我们可以通过测试数据来验证我们的算法是否正确。

public class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null || k == 1) {
            return head;
        }
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode pre = dummyHead, start = head, end = head;
        int count = 0;
        while (end != null) {
            count++;
            end = end.next;
            if (count == k && end != null) {
                ListNode next = end.next;
                end.next = null;
                pre.next = reverseList(start);
                start.next = next;
                pre = start;
                start = next;
                end = next;
                count = 0;
            } else if (end == null && count == k) {
                pre.next = reverseList(start);
            }
        }
        return dummyHead.next;
    }

    private ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }
}

测试数据验证

我们可以使用测试数据来验证一下我们的算法是否正确。

public class Test {
    public static void main(String[] args) {
        // 初始化链表
        ListNode head = new ListNode(1);
        ListNode second = new ListNode(2);
        ListNode third = new ListNode(3);
        ListNode forth = new ListNode(4);
        ListNode fifth = new ListNode(5);
        head.next = second;
        second.next = third;
        third.next = forth;
        forth.next = fifth;

        Solution solution = new Solution();

        // 验证 k = 2 的情况
        int k1 = 2;
        ListNode res1 = solution.reverseKGroup(head, k1);
        ListNode.printList(res1); // 预期输出:2->1->4->3->5

        // 验证 k = 3 的情况
        int k2 = 3;
        ListNode res2 = solution.reverseKGroup(head, k2);
        ListNode.printList(res2); // 预期输出:3->2->1->4->5
    }
}

通过测试数据的输出可以看出,我们的算法已经正确的翻转了每 k 个结点一组的单链表。

总结

在解决题目的过程中,我们掌握了如何实现单链表的 k 个一组翻转,掌握了相关的代码实现技巧和解题思路。

针对链表相关的题目,我们应该养成使用哑结点的好习惯,这样可以有效减少很多边界情况的判断。对于单链表的基本操作,我们应该熟练掌握,如单链表的创建、增加、删除、查找等操作。

希望本文能对你的学习有所帮助,使你的链表相关的算法更加得心应手。