143. Reorder List

本文介绍了一种链表重构算法,该算法将链表分为前后两部分,后半部分进行翻转,然后交替合并两部分链表。具体步骤包括使用快慢指针找到链表中点,翻转后半部分链表,最后交替合并前后两部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given a singly linked list LL0L1→…→Ln-1Ln,
reorder it to: L0LnL1Ln-1L2Ln-2→…

You must do this in-place without altering the nodes' values.

For example,
Given {1,2,3,4}, reorder it to {1,4,2,3}.

分析:先用快慢指针找到链表的中点,然后翻转链表后半部分,再和前半部分组合。需要注意的是把链表分成两半时,前半段的尾节点要置为NULL,翻转链表时也要把尾节点置为NULL。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        if(head == NULL || head->next == NULL)return;
        ListNode *fastp = head, *lowp = head, *tail = NULL;
        while(fastp != NULL && fastp->next != NULL)
        {//利用快慢指针找到链表的中点
            tail = lowp;
            fastp = fastp->next->next;
            lowp = lowp->next;
        }
        tail->next = NULL; //此时tail 指向前半段的结尾
        reverseList(lowp);//翻转链表后半段
        fastp = head;
        tail = NULL;
        while(fastp != NULL)
        {
            ListNode *tmp = lowp->next;
            lowp->next = fastp->next;
            fastp->next = lowp;
            tail = lowp;
            fastp = lowp->next;
            lowp = tmp;
        }
        if(lowp != NULL)
            tail->next = lowp;

    }
    void reverseList(ListNode* &head)
    {//翻转链表
        if(head == NULL || head->next == NULL)return;
        ListNode *pre = head, *p = pre->next;
        while(p != NULL)
        {
            ListNode *tmp = p->next;
            p->next = pre;
            pre = p;
            p = tmp;
        }
        head->next = NULL;
        head = pre;
    }
};

9.19更新

--------------

思路:


很经典的题,考到了链表的很多特性和技巧,要迅速写对写好不容易。大致思路分三步:
(1) 找到链表后一半。
(2) 将后一半节点进行反转。
(3) 将后一半节点依次插入前一半。

解法实现时有不少要注意的地方:

I. 奇偶链表长度:

偶数长度:1->2->3->4
(1) 1->2->3<-4
      |                |
      h1             h2

(2) 1->4->2->3
                 |    |
                 h1 h2 

奇数长度:1->2->3->4->5
(1) 1->2->3<-4<-5
      |                    |
      h1                h2

(2) 1->5->2->3<-4
                 |         |
                 h1     h2

(3) 1->5->2->4->3
                           |
                          h1, h2

规律:如果链表长度为L,无论L是奇数还是偶数,都从第k = L/2+1 个节点起反转。

II  如何找到第k=L/2+1个节点?

可以扫描两遍,第一遍得到L,并计算k。而第二遍扫描找到节点k。
而更好的方法则是采用快慢双指针的策略:f每次走两步,s每次走一步。当f到达最后一个节点(而不是NULL)时,s指向k。同样分奇偶长度来验证:

偶数长度:1->2->3->4
(1) 1->2->3->4->NULL
            |   |
            s  f

(2) 1->2->3->4->NULL
                |     |
                s    f

奇数长度:1->2->3->4->5
(1) 1->2->3->4->5->NULL
           |     |
           s    f

(2) 1->2->3->4->5->NULL
                |          |
                s         f

III 如何将后一半反转的链表节点依次插入前一半中?

(1) 用双指针left/right分别从左右两头开始向中间扫描。依次将right所指节点插入left节点后面。每次插入后,left需要移动两次。

(2) 终止条件:当right扫描到最后一个节点(right->next==NULL)时结束,而该节点就是原链表中的第k=L/2+1个节点。

由于步骤比较复杂,写的时候尽量分成子程序写。
class Solution {
public:
    void reorderList(ListNode *head) {
        ListNode *mid = findMid(head);
        ListNode *right = reverseList(mid);
        ListNode *left = head;
        
        while(right && right->next) {
            ListNode *target = right;
            right = right->next;
            target->next = left->next;
            left->next = target;
            left = left->next->next;
        }
    }
    
    ListNode *findMid(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast && fast->next) {
            fast = fast->next;
            if(fast->next) fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
    
    ListNode *reverseList(ListNode *head) {
        if(!head) return head;
        ListNode *pre = head, *cur = head->next;
        while(cur) {
            ListNode *temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        head->next = NULL;
        return pre;
    }
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值