字节面试题,最优解通过!

通知:吴师兄学算法训练营第十期开启报名,预计 12 月 20 开营,解锁 400 题。

一、题目描述

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,将其重新排列后变为:L0→Ln→L1→Ln-1→L2→Ln-2→…

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

示例1:

给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

示例2:

给定链表 1->2->3->4, 重新排列为 1->4->2->3.

字节面试题,最优解通过!

二、题目解析

这题属于是链表题的大杂烩了,包含了链表题型中会涉及到的很多思想,非常考研基本功,也难怪字节会考察,同时还希望你能给出最优解。

字节面试题,最优解通过!
图片来源于 LeetCode 143 号问题题解评论区

这道题目很考察基本功和观察能力,最终的结果就是将原链表的前半部分和原链表的后半部分反转之后的链表进行合并得到的

所以,需要执行以下三个操作。

  • 1、寻找出原链表的中点,把链表划分为两个区域
  • 2、将右边的链表进行反转
  • 3、把这两个区域进行交错合并

1、使用快慢指针寻找链表中点

在链表的头节点设置两个指针 slow、fast,同时将它们向后移动。

字节面试题,最优解通过!

每一次,slow 向后移动一步,fast 向后移动两步。

字节面试题,最优解通过!
字节面试题,最优解通过!

于是,找到了中间节点 5,把链表划分为两个区域。

字节面试题,最优解通过!

2、将右边的链表进行反转

字节面试题,最优解通过!

3、把这两个区域进行交错合并

属于归并排序的降维版本,这个操作不了解的话可以复习一下归并排序

三、参考代码

// LeetCode 100 题精讲:https://mp.weixin.qq.com/s/yznC53g46phq3qF7V4-obA
// 作者:程序员吴师兄
// 重排链表(LeetCode 143):https://leetcode.cn/problems/reorder-list/
class Solution {
    public void reorderList(ListNode head) {
    // a、寻找出原链表的中点,把链表划分为两个区域
        // b、将右边的链表进行反转
        // c、把这两个区域进行交错合并
      
        // 1、使用快慢指针寻找出链表的中点来 
       // *******************************************************
        // 对于 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
        // 中间节点值为 5
        // 所以左边区域为 1 -> 2 -> 3 -> 4 -> 5
        // 右边区域为 6 -> 7 -> 8
        // 但在视频讲解中,我把 5 归为了右边区域,这是一个错误
        // 虽然这个错误并不影响结果,因为合并过程都是一样的逻辑
        // *******************************************************
        ListNode mid = middleNode(head);

        // 2、基于 mid 这个中点,将链表划分为两个区域

        // 左边的区域开头节点是 head
        ListNode leftHead = head;

        // 右边的区域开头节点是 mid.next
        ListNode rightHead = mid.next;

        // 将链表断开,就形成了两个链表了
        mid.next = null;

        // 3、将右边的链表进行反转
        rightHead = reverseList(rightHead);

        // 4、将这两个链表进行合并操作,即进行【交错拼接】
        while( leftHead != null && rightHead != null){

            // 拼接过程如下
            // 5、先记录左区域、右区域【接下来将有访问的两个节点】
            ListNode leftHeadNext = leftHead.next;

            ListNode rightHeadNext = rightHead.next;

            // 6、左边连接右边的开头
            leftHead.next = rightHead;

            // 7、leftHead 已经处理好,移动到下一个节点,即刚刚记录好的节点
            leftHead = leftHeadNext;

            // 8、右边连接左边的开头
            rightHead.next = leftHead;

            // 9、rightHead 已经处理好,移动到下一个节点,即刚刚记录好的节点
            rightHead = rightHeadNext;
            
        }

    }

    // LeetCode 876 : 链表的中间节点
    public ListNode middleNode(ListNode head) {

        ListNode fast = head;

        ListNode slow = head;

        while(fast != null && fast.next != null){

            fast = fast.next.next;

            slow = slow.next;
        }

        return slow;

    }

    // LeetCode 206 : 反转链表
    public ListNode reverseList(ListNode head) {

        // 寻找递归终止条件
        // 1、head 指向的结点为 null 
        // 2、head 指向的结点的下一个结点为 null 
        // 在这两种情况下,反转之后的结果还是它自己本身
        if( head == null || head.next == null)  return head;

        // 不断的通过递归调用,直到无法递归下去,递归的最小粒度是在最后一个节点
        // 因为到最后一个节点的时候,由于当前节点 head 的 next 节点是空,所以会直接返回 head
        ListNode cur = reverseList(head.next);

        // 比如原链表为 1 --> 2 --> 3 --> 4 --> 5
        // 第一次执行下面代码的时候,head 为 4,那么 head.next = 5
        // 那么 head.next.next 就是 5.next ,意思就是去设置 5 的下一个节点
        // 等号右侧为 head,意思就是设置 5 的下一个节点是 4

        // 这里出现了两个 next
        // 第一个 next 是「获取」 head 的下一节点
        // 第二个 next 是「设置」 当前节点的下一节点为等号右侧的值
        head.next.next = head;


        // head 原来的下一节点指向自己,所以 head 自己本身就不能再指向原来的下一节点了
        // 否则会发生无限循环
        head.next = null;

        // 我们把每次反转后的结果传递给上一层
        return cur;
    }
}

玩转 LeetCode 高频 100 题系列正在更新,全系列题目和所有视频讲解可点击下方图片查看:

字节面试题,最优解通过!

数组篇

1、LeetCode 283:移动零

2、LeetCode 75:颜色分类

3、LeetCode 26:删除有序数组中的重复项

4、LeetCode 88:合并两个有序数组

链表篇

1、LeetCode 876、链表的中间结点

2、字节实习一面,不画图,真的想不清楚!

系列介绍:LeetCode 刷多少题能进大厂面试?