myesn

myEsn2E9

hi
github

链表取前一半翻转,再跟后一半连在一起

这道题可以拆分为两道算法题,分别是:[链表的中间结点] 和 [反转链表]。

链表的中间结点(双指针)#

借助快慢双指针 fastslow[快指针 fast ] 每轮走 2 步,[慢指针 slow ] 每轮走 1 步

fast 的步数恒为 slow 的 2 倍,因此当快指针遍历完链表时,慢指针就指向链表中间节点。而由于长度为偶数的链表有两个中间节点,因此需要分两种情况考虑:

  • 链表长度为奇数:当 fast 走到链表 [尾节点] 时,slow 正好走到 [中间节点]。
  • 链表长度为偶数:当 fast 走到 [null] 时(越过 [尾节点] 后),slow 正好走到 [第二个中间节点]。

总结以上规律,应该fast 遇到或越过尾节点时跳出循环,并返回 slow 即可

// 长度为奇数的链表
ListNode oddHead = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, null)))));
// 长度为偶数的链表
ListNode evenHead = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6, null))))));

Console.WriteLine("odd head middleNode val: {0}", MiddleNode(oddHead).Val);
Console.WriteLine("even head middleNode val: {0}", MiddleNode(evenHead).Val);

// 链表的中间结点
ListNode MiddleNode(ListNode head)
{
    // 初始化快慢指针 fast, slow 皆指向链表头节点 head
    ListNode? fast = head, slow = head;

    // 当 fast 遇到过越过尾节点时跳出循环
    while (fast != null && fast.Next != null)
    {
        fast = fast.Next.Next;
        slow = slow!.Next;
    }

    return slow!;
}

class ListNode
{
    public int Val { get; set; }
    public ListNode? Next { get; set; }
    public ListNode(int val, ListNode? next)
    {
        Val = val;
        Next = next;
    }
}

若题目要求返回「第一个中间节点」,则应在 fast 遇到尾节点或其前驱节点 时跳出循环。此时,修改判断条件为 while fast.next and fast.next.next 即可。

反转链表#

解法一:迭代(双指针)#

遍历链表,并在访问各节点时修改 next 引用指向,具体流程看代码注释。
image
image
image

// 初始化链表
ListNode node = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, null)))));

ListNode reverseNode = ReverseNode(node);
while (reverseNode != null)
{
    Console.WriteLine("reverseNode val: {0}", reverseNode.Val);
    reverseNode = reverseNode.Next;
}

// 反转链表:迭代(双指针)
ListNode ReverseNode(ListNode head)
{
    // 初始化 pre, cur 分别指向 null 和 头节点
    ListNode pre = null, cur = head;
    while (cur != null)
    {
        ListNode tmp = cur.Next; // 暂存后继节点

        cur.Next = pre;          // 修改引用指向
        pre = cur;               // 暂存当前节点

        cur = tmp;               // 访问下一节点
    }

    return pre;
}

class ListNode
{
    public int Val { get; set; }
    public ListNode? Next { get; set; }
    public ListNode(int val, ListNode? next)
    {
        Val = val;
        Next = next;
    }
}

最终代码#

// Q: 链表取前一半翻转,再跟后一半连接

// 初始化长度为奇数的链表
using System.Diagnostics;

ListNode oddHead = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, null)))));
// 初始化长度为偶数的链表
ListNode evenHead = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6, null))))));

// 按照要求将链表取前一半翻转,再跟后一半连接后的链表
Console.WriteLine("长度为奇数的链表:");

Stopwatch sw = Stopwatch.StartNew();
ListNode? finalOddHead = ReverseLeftOfMiddleAndCombineRightOfMiddle(oddHead);
sw.Stop();
Console.WriteLine($"耗时: {sw.ElapsedMilliseconds}ms");

while (finalOddHead != null)
{
    Console.WriteLine(finalOddHead.Val);
    finalOddHead = finalOddHead.Next;
}

// 按照要求将链表取前一半翻转,再跟后一半连接后的链表
Console.WriteLine("长度为偶数的链表:");

sw = Stopwatch.StartNew();
ListNode? finalEvenHead = ReverseLeftOfMiddleAndCombineRightOfMiddle(evenHead);
sw.Stop();
Console.WriteLine($"耗时: {sw.ElapsedMilliseconds}ms");

while (finalEvenHead != null)
{
    Console.WriteLine(finalEvenHead.Val);
    finalEvenHead = finalEvenHead.Next;
}

ListNode ReverseLeftOfMiddleAndCombineRightOfMiddle(ListNode head)
{
    // 找到链表的中点
    // 使用快慢指针 fast 和 slow,快指针每次走 2 步,快指针的步数恒为慢指针的 2 倍,慢指针每次走一步,当快指针遇到或越过尾节点时跳出循环,返回 slow 即可,其代表中间节点。
    ListNode fast = head, slow = head;
    while (fast != null && fast.Next != null)
    {
        fast = fast.Next.Next;
        slow = slow.Next;
    }

    // 当链表长度为奇数时,slow 刚好是最中间的节点
    // 当链表长度为偶数时,slow 是中间两个节点的最后一个节点
    ListNode middleNode = slow;

    // 翻转左侧链表
    // 使用迭代(双指针)
    // 这里将 pre 指向中点,这样最后翻转左侧链表后,左侧链表的最后一个节点就指向右侧链表(链表中点),就实现将翻转后链表的和右侧链表连接
    ListNode pre = middleNode, cur = head;
    // 当前循环的节点不为空,并且当前循环的节点在中点之前,即只翻转中点左侧的链表
    while (cur != null && cur.Val != middleNode.Val)
    {
        // 暂存后继节点
        ListNode tmp = cur.Next;

        // 修改引用指向
        cur.Next = pre;
        // 暂存当前节点
        pre = cur;

        // 访问下一个节点
        cur = tmp;
    }

    return pre;
}

// 单向链表结构
class ListNode
{
    public int Val { get; set; }
    public ListNode? Next { get; set; }
    public ListNode(int val, ListNode? next)
    {
        Val = val;
        Next = next;
    }
}

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。