【刷题之路Ⅱ】LeetCode 143. 重排链表

【刷题之路Ⅱ】LeetCode 143. 重排链表

  • 一、题目描述
  • 二、解题
    • 1、方法1——线性表辅助
      • 1.1、思路分析
      • 1.2、代码实现
    • 2、方法2——中间节点+反转链表+合并链表
      • 2.1、思路分析
      • 2.2、代码实现

一、题目描述

原题连接: 143. 重排链表
题目描述:
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

【刷题之路Ⅱ】LeetCode 143. 重排链表_第1张图片
输入: head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:

【刷题之路Ⅱ】LeetCode 143. 重排链表_第2张图片
输入: head = [1,2,3,4,5]
输出: [1,5,2,4,3]

提示:
链表的长度范围为 [1, 5 * 104]
1 <= node.val <= 1000

二、解题

1、方法1——线性表辅助

1.1、思路分析

既然链表不支持随机访问而顺序表支持随机访问,那我们就可以想办法先将链表中的节点放到一个节点数组中,然后再按题目给的顺序访问各个节点然后连接成一个新的链表即可。
当把节点存入数组中后,我们让两个指针分别指向数组的第一个元素和最后一个元素:
【刷题之路Ⅱ】LeetCode 143. 重排链表_第3张图片
每次当我们把NodeArray[left]的next指向NodeArray[right]之后就让left向后走一步,然后再让NodeArray[right]的next指向NodeArray[left],再让right向前走一步:
【刷题之路Ⅱ】LeetCode 143. 重排链表_第4张图片
这样就能巧妙地完成题目的要求了。
当left与right相等时我们就可以停止循环,然后将NodeArray[left]的next指向NULL即可:
【刷题之路Ⅱ】LeetCode 143. 重排链表_第5张图片

1.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

void reorderList(struct ListNode* head){
    if (NULL == head || NULL == head->next) {
        return head;
    }
    struct ListNode *cur = head;
    int len = 0;
    // 先算出链表的长度
    while (cur) {
        len++;
        cur = cur->next;
    }
    // 创建一个节点数组
    struct ListNode **NodeArray = (struct ListNode**)malloc(len * sizeof(struct ListNode*));
    if (NULL == NodeArray) {
        perror("malloc fail");
        return;
    }
    cur = head;
    int n = 0;
    // 将链表中的节点拷贝到数组中
    while (cur) {
        NodeArray[n] = cur;
        n++;
        cur = cur->next;
    }
    // 完成重排
    int left = 0;
    int right = len - 1;
    while (left < right) {
        NodeArray[left]->next = NodeArray[right];
        left++;
        if (left == right) {
            break;
        }
        NodeArray[right]->next = NodeArray[left];
        right--;
    }
    NodeArray[left]->next = NULL;
}

时间复杂度:O(n),n为链表的长度。
空间复杂度:O(n),我们需要额外的n个空间来存储指向各个节点的指针。

2、方法2——中间节点+反转链表+合并链表

2.1、思路分析

通过上一方法我们其实可以发现,重排后的链表其实就是等同于前一半链表和后一半的反转后的链表合并后的结果:
【刷题之路Ⅱ】LeetCode 143. 重排链表_第6张图片
所哟们可以先找到链表的中间节点,然后将中间节点后的链表(不包括中间节点)反转,再让原链表在中间节点处断开,然后再交叉和并这两条链表即可。
找链表的中间节点我们可以沿用[876. 链表的中间结点]中经典的快慢指针的方法。
反转链表我们可以沿用[206. 反转链表]中的头插法。

2.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

// 先写一个函数,找到链表的中间节点
struct ListNode* middleNode(struct ListNode* head) {
	struct ListNode* slow = head; // 慢指针,一次走一步
	struct ListNode* fast = head; // 快指针,一次走两步
	while (fast && fast->next) {
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}

// 再写一个函数,反转一个链表
 struct ListNode* reverseList(struct ListNode* head) {
     struct ListNode* Cur = head;
     struct ListNode* newhead = NULL; // 新的头指针
     struct ListNode* Next = NULL; // 保存cur的下一个节点,以辅助头插
     while (Cur) {
         Next = Cur->next;
         // 头插
         Cur->next = newhead;
         newhead = Cur;

         // 迭代往后走
         Cur = Next;
     }
     return newhead;
 }

void reorderList(struct ListNode* head){
    if (NULL == head || NULL == head->next) {
        return head;
    }
    // 先找到链表的中间节点
    struct ListNode *midNode = middleNode(head);

    // 再将中间节点后面的链表反转
    struct ListNode *head2 = reverseList(midNode->next);

    // 在中间节点处断开
    midNode->next = NULL;
    
    // 合并head和head2两条链表
    struct ListNode *cur1 = head;
    struct ListNode *next1 = NULL;
    struct ListNode *cur2 = head2;
    struct ListNode *next2 = NULL;
    while (cur1 && cur2) {
        next1 = cur1->next;
        next2 = cur2->next;
        cur2->next = cur1->next;
        cur1->next = cur2;
        cur1 = next1;
        cur2 = next2;
    }
}

时间复杂度:O(n),n为链表的长度。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

你可能感兴趣的:(刷题之路——中等篇,链表,leetcode,数据结构,c语言,算法)