0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

重新排列一个单链表

算法与数据结构 来源:吴师兄学算法 作者:吴师兄 2022-10-10 09:39 次阅读

一、题目描述

给定一个单链表 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.

1a525330-47bb-11ed-a3b6-dac502259ad0.png

二、题目解析

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

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

1、寻找出原链表的中点,把链表划分为两个区域

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

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

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

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

1a9fbd82-47bb-11ed-a3b6-dac502259ad0.png

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

1acce5aa-47bb-11ed-a3b6-dac502259ad0.png1b0d0af4-47bb-11ed-a3b6-dac502259ad0.png

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

1b2a6f86-47bb-11ed-a3b6-dac502259ad0.png

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

1b68b21e-47bb-11ed-a3b6-dac502259ad0.png

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

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

三、参考代码

1、Java 代码

//登录AlgoMooc官网获取更多算法图解
//https://www.algomooc.com
//作者:程序员吴师兄
//代码有看不懂的地方一定要私聊咨询吴师兄呀
//重排链表(LeetCode 143):https://leetcode.cn/problems/reorder-list/
classSolution{
publicvoidreorderList(ListNodehead){
//a、寻找出原链表的中点,把链表划分为两个区域
//b、将右边的链表进行反转
//c、把这两个区域进行交错合并

//1、使用快慢指针寻找出链表的中点来
//*******************************************************
//对于1->2->3->4->5->6->7->8
//中间节点值为5
//所以左边区域为1->2->3->4->5
//右边区域为6->7->8
//但在视频讲解中,我把5归为了右边区域,这是一个错误
//虽然这个错误并不影响结果,因为合并过程都是一样的逻辑
//*******************************************************
ListNodemid=middleNode(head);

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

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

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

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

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

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

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

ListNoderightHeadNext=rightHead.next;

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

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

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

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

}

}

//LeetCode876:链表的中间节点
publicListNodemiddleNode(ListNodehead){

ListNodefast=head;

ListNodeslow=head;

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

fast=fast.next.next;

slow=slow.next;
}

returnslow;

}

//LeetCode206:反转链表
publicListNodereverseList(ListNodehead){

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

//不断的通过递归调用,直到无法递归下去,递归的最小粒度是在最后一个节点
//因为到最后一个节点的时候,由于当前节点head的next节点是空,所以会直接返回head
ListNodecur=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;

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

2、C++ 代码

classSolution{
public:
voidreorderList(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=nullptr;

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

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

//拼接过程如下
//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;

}
}



ListNode*middleNode(ListNode*head){
ListNode*slow=head;
ListNode*fast=head;
while(fast!=nullptr&&fast->next!=nullptr){
slow=slow->next;
fast=fast->next->next;
}
returnslow;
}


public:
ListNode*reverseList(ListNode*head){

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

//不断的通过递归调用,直到无法递归下去,递归的最小粒度是在最后一个节点
//因为到最后一个节点的时候,由于当前节点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=nullptr;

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

}

};

3、Python 代码

classSolution:
defreorderList(self,head:ListNode)->None:
#a、寻找出原链表的中点,把链表划分为两个区域
#b、将右边的链表进行反转
#c、把这两个区域进行交错合并

#1、使用快慢指针寻找出链表的中点来
#*******************************************************
#对于1->2->3->4->5->6->7->8
#中间节点值为5
#所以左边区域为1->2->3->4->5
#右边区域为6->7->8
#但在视频讲解中,我把5归为了右边区域,这是一个错误
#虽然这个错误并不影响结果,因为合并过程都是一样的逻辑
#*******************************************************
mid=self.middleNode(head)

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

#左边的区域开头节点是head
leftHead=head

#右边的区域开头节点是mid.next
rightHead=mid.next

#将链表断开,就形成了两个链表了
mid.next=None

#3、将右边的链表进行反转
rightHead=self.reverseList(rightHead)

#4、将这两个链表进行合并操作,即进行【交错拼接】
whileleftHeadandrightHead:

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

rightHeadNext=rightHead.next

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

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

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

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



defmiddleNode(self,head:ListNode)->ListNode:
slow=fast=head
whilefastandfast.next:
slow=slow.next
fast=fast.next.next
returnslow

defreverseList(self,head):
"""
:typehead:ListNode
ListNode
"""
#寻找递归终止条件
#1、head指向的结点为null
#2、head指向的结点的下一个结点为null
#在这两种情况下,反转之后的结果还是它自己本身
if(head==Noneorhead.next==None):
returnhead

#不断的通过递归调用,直到无法递归下去,递归的最小粒度是在最后一个节点
#因为到最后一个节点的时候,由于当前节点head的next节点是空,所以会直接返回head
cur=self.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.next=None

#我们把每次反转后的结果传递给上一层
returncur

四、复杂度分析

时间复杂度:O(N),其中 N 是链表中的节点数。

空间复杂度:O(1)。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    102998
  • python
    +关注

    关注

    51

    文章

    4677

    浏览量

    83473

原文标题:重排链表(LeetCode 143)

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    数据结构:单链表的排序

    给定一个单链表的头结点head(该结点有值),长度为n的无序单链表,对其按升序排序后,返回新链表。如当输入链表 {3,1,4,5,2} 时,经升序
    的头像 发表于 11-30 13:56 441次阅读
    数据结构:单<b class='flag-5'>链表</b>的排序

    C语言单向链表

    strName[16];//用指针的话会出访问冲突异常 struct NODE *next;};//创建具有n节点的链表,从键盘输入数据将其初始化,并返回
    发表于 05-22 15:53

    Linux内核的链表操作

    主要是访问的顺序性和组织链的空间损失。通常链表数据结构至少应包含两域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表
    发表于 08-29 11:13

    链表在MCU编程时的应用

    数组在程序中的应用是很普遍的,下面说说我们MCU程序员如何应用链表这种数据结构来更好的设计程序。先描述下我们要做的事情:主控系统中需要管理多种子设备,每个加入主控的子设备都有编号
    发表于 11-20 09:10

    玩转C语言链表-链表各类操作详解

    ;  return head;  }  对链表进行选择排序的基本思想就是反复从还未排好序的那些节点中,选出键值(就是用它排序的字段,我们取学号num为键值)最小的节点,依次重新组合成
    发表于 09-18 13:30

    请问空闲链表块怎么连成链表的?

    大家好,我在看内存管理任务这章时,定位到OSMemCreate此函数,很不明白空闲链表块怎么连成链表的?p_link = (void
    发表于 10-31 02:10

    链表代码头结点数据无效

    GetElem(LinkList L,int i,data_t *data)//读取链表的第i元素{int j;LinkList p;//工作指针p = L->next;j = 1;while(p
    发表于 03-27 00:43

    约瑟夫环之循环链表这个程序题目大家知道做吗

    题目:   n个人围成圈(编号依次为:0,1,2...n-1),从第一个人开始报数,1,2,……数到m者出列,再从下一个开始重新报数,数到m者再出列……。 下面的程序中,用不带附加表
    发表于 10-27 11:08

    链表的缺陷是什么

    链表定的缺陷,就是单向性,只能从结点到下一个节点,而不能访问到上
    发表于 07-14 08:09

    RT-Thread内核中链表的使用与实现

    1. 链表与数组数组:线性数据结构,存放的数据的类型是样的,而且他们在内存中的排布是有序排列的。因此数组的优势就是数据连续,随机访问速度快,定义好了就不好在改变大小.
    发表于 04-01 12:01

    OpenHarmony中的HDF链表及其迭代器

    成员的值设置成第1节点的地址。因为链表只支持往方向查找,不支持往回查找,如上面的错误范例。如果root记录的是第二
    发表于 08-30 10:31

    OpenHarmony中的HDF链表及其迭代器

    节点的地址。因为链表只支持往方向查找,不支持往回查找,如上面的错误范例。如果root记录的是第二节点地址,则第
    发表于 09-05 11:38

    如何使用EB tresos自动重新排列和计算端口引脚ID?

    我指的是修改bootloader的附件文档,只是参考3.4,你能告诉我如何使用EB tresos重新排列和自动计算端口pin ID吗?而且如红圈所指,我对新的端口ID很困惑,GPIO1_6_LED
    发表于 03-17 09:03

    C语言实现单链表举例

    所谓链表,就是用一组任意的存储单元存储线性表元素的一种数据结构。链表又分为单链表、双向链表和循环链表等。我们先讲讲单
    发表于 07-11 16:40 87次下载
    C语言实现单<b class='flag-5'>链表</b>举例

    如何利用AD批量修改原理图元件符号

    我们需要将原理图上的元件名称重新排列。单击“Tools"一“Annotate Schematics..."进行元件排列
    的头像 发表于 11-08 09:33 5.4w次阅读