题目描述

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例1:

No.143 重排链表 (Medium) - 图1

  1. 输入: head = [1,2,3,4]
  2. 输出: [1,4,2,3]

示例2:
No.143 重排链表 (Medium) - 图2

  1. 输入: head = [1,2,3,4,5]
  2. 输出: [1,5,2,4,3]

思路

  1. 将链表拆成两个,l1 和 l2;
  2. 将l2翻转;
  3. 依次连接两个链表中的结点。

涉及的知识点

  1. 如何找到链表中点?

用快慢指针,

  • 如果是奇数个结点,快指针走到末尾的时候,慢指针刚好走到中点;
  • 如果是偶数个结点,快指针走到null的时候,慢指针刚好走到l1的末尾;
  1. 链表如何翻转?

递归或者迭代。最好把迭代背下来。

  1. class Solution {
  2. public void reorderList(ListNode head) {
  3. // 特殊处理
  4. // 当结点数小于3时,不用做任何操作,直接返回原链表即可
  5. if (head == null || head.next == null || head.next.next == null) {
  6. return;
  7. }
  8. ListNode slow = head;
  9. ListNode fast = head;
  10. // 注意这里的判断方式,这样子写可以让慢指针最后指向第一个链表的末尾
  11. while (fast.next != null && fast.next.next != null) {
  12. fast = fast.next.next;
  13. slow = slow.next;
  14. }
  15. ListNode l1 = head;
  16. // 链表的翻转,可以用递归,不行就用迭代即可。
  17. ListNode l2 = reverse(slow.next);
  18. slow.next = null;
  19. // l1 长度是 大于 l2的,所以l2优先到null
  20. while (l2 != null) {
  21. ListNode temp = l2.next;
  22. l2.next = l1.next;
  23. l1.next = l2;
  24. // 精髓在这里
  25. l1 = l2.next;
  26. l2 = temp;
  27. }
  28. }
  29. private ListNode reverse(ListNode start) {
  30. if (start.next == null) {
  31. return start;
  32. }
  33. ListNode l2 = reverse(start.next);
  34. start.next.next = start;
  35. start.next = null;
  36. return l2;
  37. }
  38. }

注意事项:

  1. 快慢指针停止的判断条件:

    1. // 注意这里的判断方式,这样子写可以让慢指针最后指向第一个链表的末尾
    2. while (fast.next != null && fast.next.next != null) {
    3. fast = fast.next.next;
    4. slow = slow.next;
    5. }

    之所以不用fast != null && fast.next != null是因为,如果这样判断就会导致最后在停止的时候,慢指针指向了第二个链表的开头,这样不利于我们将链表断开成两个新的链表。可以在纸上画两个例子模拟一下。

  2. 理解依次连接两个链表的操作:

    1. // l1 长度是 大于 l2的,所以l2优先到null
    2. while (l2 != null) {
    3. ListNode temp = l2.next;
    4. l2.next = l1.next;
    5. l1.next = l2;
    6. // 精髓在这里
    7. l1 = l2.next;
    8. l2 = temp;
    9. }