题目链接

题目介绍

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

进阶:
你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:
image.png
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:
image.png
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
示例 3:
输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]
示例 4:
输入:head = [1], k = 1
输出:[1]

解题思路

  1. 创建节点hair
  2. 将hair节点的下一个节点指向head
  3. 定义节点cachePre,将cachePre指向hair。用来缓存子链表的上一个节点
  4. 循环 当head不等于null时
    • 定义尾节点tail,将tail指向cachePre (如果tail指向head的话,移动k个位置时,就有了K+1个节点
    • 循环k次 (主要意思为判断子链表的长度是否大于等于k)
      • 将tail往后移动
      • 判断tail是否为空,若为空,为数组元素个数小于k个,不需要反转,直接返回hair.next
    • 定义节点cacheNext缓存子链表的下一个节点
    • 反转子链表(单独函数,返回值要是数组,包括头节点和尾节点)
    • 重新组织头节点和尾节点(head=反转后的头节点;tail=反转后的尾节点)
    • 使用缓存的cachePre和cacheNext把子链表重新接回原链表
    • 准备开始下一个子链表,将cachePre,head依次往后移动到当前的tail和tail.next处
  5. 返回值,hair.next


链表面试专题总结

:::tips 📌 有个小坑
本来思路还算清晰,结果这一行给整懵逼了,绕了好久

  1. 官方在反转单链表方法myReverse中定义了ListNode prev = tail.next;这句话其实会让人误解,闹不懂啥意思。如果这样写,翻转过后其实已经和下一段的头完成连接了,出来后只需要补上和上一段尾部的连接就好了
  2. 也可以写成ListNode prev = null;这样的话,在外面需要补上【上一段尾部的连接 和 下一段头部的链接】

区别见17行,如果定义了ListNode prev = tail.next;的话,就不需要缓存下一段的头节点
区别见23行,如果定义了ListNode prev = tail.next;的话,就不需要链接下一段的头节点
区别见33行,是否定义ListNode prev = tail.next;还是ListNode prev = null;

第2中写法,思路较清晰 :::

代码

  1. class Solution {
  2. public ListNode reverseKGroup(ListNode head, int k) {
  3. ListNode hair = new ListNode(0);//因为头节点前面没有pre,所以创建节点
  4. hair.next = head;//将创建的节点与head连接
  5. ListNode pre = hair;//缓存子链表的上一个节点pre
  6. while (head != null) {
  7. ListNode tail = pre;//tail为子链表的尾节点
  8. // 查看剩余部分长度是否大于等于 k
  9. for (int i = 0; i < k; ++i) {
  10. tail = tail.next;//移动K个,移动到子链表的末尾。
  11. if (tail == null) {
  12. return hair.next;
  13. }
  14. }
  15. //当前链表长度为head --> tail
  16. ListNode[] reverse = myReverse(head, tail);//反转单链表
  17. head = reverse[0];//head为子链表的头节点
  18. tail = reverse[1];//tail为子链表的尾节点
  19. // 把子链表重新接回原链表
  20. pre.next = head;
  21. //开始下一个子链表。将pre,head依次往后移动
  22. pre = tail;
  23. head = tail.next;
  24. }
  25. return hair.next;
  26. }
  27. public ListNode[] myReverse(ListNode head, ListNode tail) {
  28. ListNode prev = tail.next;
  29. ListNode p = head;
  30. while (prev != tail) {
  31. ListNode nex = p.next;
  32. p.next = prev;
  33. prev = p;
  34. p = nex;
  35. }
  36. return new ListNode[]{tail, head};
  37. }
  38. }
  1. class Solution {
  2. public ListNode reverseKGroup(ListNode head, int k) {
  3. ListNode hair = new ListNode(0);//因为头节点前面没有pre,所以创建节点
  4. hair.next = head;//将创建的节点与head连接
  5. ListNode pre = hair;//缓存子链表的上一个节点pre
  6. while (head != null) {
  7. ListNode tail = pre;//tail为子链表的尾节点
  8. // 查看剩余部分长度是否大于等于 k
  9. for (int i = 0; i < k; ++i) {
  10. tail = tail.next;//移动K个,移动到子链表的末尾。
  11. if (tail == null) {
  12. return hair.next;
  13. }
  14. }
  15. //当前链表长度为head --> tail
  16. ListNode nex = tail.next;//缓存子链表的下一个节点
  17. ListNode[] reverse = myReverse(head, tail);//反转单链表
  18. head = reverse[0];//head为子链表的头节点
  19. tail = reverse[1];//tail为子链表的尾节点
  20. // 把子链表重新接回原链表
  21. pre.next = head;
  22. tail.next = nex;
  23. //开始下一个子链表。将pre,head依次往后移动
  24. pre = tail;
  25. head = tail.next;
  26. }
  27. return hair.next;
  28. }
  29. public ListNode[] myReverse(ListNode head, ListNode tail) {
  30. ListNode prev = null;
  31. ListNode cur = head;
  32. while (prev != tail) {
  33. ListNode nex = cur.next;
  34. cur.next = prev;
  35. prev = cur;
  36. cur = nex;
  37. }
  38. return new ListNode[]{tail, head};
  39. }
  40. }