[关闭]
@Catyee 2021-09-20T19:53:12.000000Z 字数 2787 阅读 419

链表反转

数据结构与算法


一、反转整个链表

反转整个链表是leetcode第206题,题目表述:
反转一个单链表。

反转链表有三种方式:
1、递归 2、迭代 3、头插法
递归需要有额外的递归栈空间,空间复杂度O(n),头插法需要一个额外的虚拟头部,迭代不需要额外的空间,但是实现相对更加复杂。

  1. // 递归
  2. public ListNode revert(ListNode head) {
  3. if (head == null || head.next == null) {
  4. return head;
  5. }
  6. // 不要陷入递归细节,而应该按递归函数定义整体理解
  7. ListNode last = revert(head.next);
  8. head.next.next = head;
  9. head.next = null;
  10. return last;
  11. }
  12. // 迭代
  13. public ListNode revert(ListNode head) {
  14. if (head == null || head.next == null) {
  15. return head;
  16. }
  17. ListNode pre = null;
  18. ListNode cur = head;
  19. ListNode next = null;
  20. while(cur != null) {
  21. // 先保存下一个节点,防止断链
  22. next = cur.next;
  23. // 反转
  24. cur.next = pre;
  25. // 移动指针
  26. pre = cur;
  27. cur = next;
  28. }
  29. return pre;
  30. }
  31. // 头插法
  32. public ListNode revert(ListNode head) {
  33. if (head == null || head.next == null) {
  34. return head;
  35. }
  36. // 虚拟头部
  37. ListNode newHead = new ListNode(-1);
  38. ListNode next = null;
  39. while(head != null) {
  40. // 先保存下一个节点,防止断链
  41. next = head.next;
  42. head.next = newHead.next;
  43. newHead.next = head;
  44. head = next;
  45. }
  46. return newHead.next;
  47. }

二、反转链表的前n个节点

题目描述:
将链表的前n个节点进行反转(1 <= n <= 链表长度)

同样有三种实现方式:递归、迭代和头插法

  1. // 递归
  2. // 递归结束之后要连接剩余节点,所以要额外一个变量记录剩余节点
  3. ListNode sucessor = null; //记录后驱节点
  4. public ListNode reverseN(ListNode head, int n) {
  5. if (n == 1) {
  6. sucessor = head.next;
  7. return head;
  8. }
  9. ListNode last = reverseN(head.next, n - 1);
  10. head.next.next = head;
  11. head.next = sucessor;
  12. return last;
  13. }
  1. // 迭代
  2. public ListNode reverseN(ListNode head, int n) {
  3. if (n == 1) {
  4. return head;
  5. }
  6. ListNode pre = null;
  7. ListNode cur = head;
  8. ListNode next = null;
  9. while (cur != null && n >= 1) {
  10. next = cur.next;
  11. cur.next = pre;
  12. pre = cur;
  13. cur = next;
  14. n--;
  15. }
  16. head.next = next;
  17. return pre;
  18. }
  1. // 头插法
  2. public ListNode reverseN(ListNode head, int n) {
  3. if (n == 1) {
  4. return head;
  5. }
  6. // 虚拟头部
  7. ListNode newHead = new ListNode(-1);
  8. ListNode cur = head;
  9. ListNode next = null;
  10. while(cur != null && n >= 1) {
  11. next = cur.next;
  12. cur.next = newHead.next;
  13. newHead.next = cur;
  14. cur = next;
  15. n--;
  16. }
  17. head.next = next;
  18. return newHead.next;
  19. }

三、反转区间的链表节点

leetcode第92题,题目表述:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。1 ≤ m ≤ n ≤ 链表长度。

思考:如果m=1, 则问题退化为反转链表的前n个节点。所以可以向右移动节点知道m的位置,以此节点为头节点进行反转

  1. public ListNode reverseBetween(ListNode head, int m, int n) {
  2. // 如果m=1 问题退化为反转链表的前n个节点
  3. if (m == 1) {
  4. return reverseNR(head, n);
  5. }
  6. // 转化为子问题(右移节点,区间变动直到m=1,完成问题的退化)
  7. head.next = reverseBetween(head.next, m - 1, n - 1);
  8. return head;
  9. }

四、k个一组反转链表

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

递归思考:
当我们将链表的前k个节点反转之后,剩下的工作是k个一组反转剩余链表。可以看到剩余工作完全不变,只是规模减小,即递归子问题。
而递归出口就是题目中所说的“如果节点总数不是k的整数倍,那么请将最后剩余的节点保持原有顺序”。

如果要进行实现,首先要解决的是k个节点的反转,k个节点反转可以认为是区间反转,但是区间反转结束之后要能够将链表重新连接起来,所以为了简单操作,不再以下标的[m,n]来做为参数,而是以边界节点[a,b)做为参数,这样可以方便链表的前后连接,所以反转函数如下:

  1. // 反转a到b区间内的节点 (注意是左闭右开)
  2. // 可以看到这部分代码和反转整个链表非常相似,实际上反转整个链表可以看作是反转head到null区间[head, null)的节点
  3. public ListNode revertBetween(ListNode a, ListNode b) {
  4. ListNode pre = null;
  5. ListNode cur = a;
  6. ListNode next = null;
  7. while (cur != b) {
  8. next = cur.next;
  9. cur.next = pre;
  10. pre = cur;
  11. cur = next;
  12. }
  13. return pre;
  14. }

有了反转k个节点的函数之后,然后进行整个链表反转的工作:

  1. public ListNode reverseKGroup(ListNode head, int k) {
  2. ListNode a = head;
  3. ListNode b = head;
  4. // 构造[a, b)的区间,即a到b中间有k个节点(包含a但不包含b)
  5. for (int i = 0; i < K; i++) {
  6. if (b == null) {
  7. return head;
  8. }
  9. b = b.next;
  10. }
  11. ListNode newHead = revertBetween(a, b);
  12. a.next = reverseKGroup(b, k);
  13. return newHead;
  14. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注