题目概述(简单难度)
现有一链表的头指针 ListNode* pHead
,给一定值x,编写一段代码将所有小于x的节点排在其余结点之前,且不能改变原来的数据顺序,返回
重新排列后的链表的头指针
。
在此附上牛客网的链接:
点击此处进入牛客网
思路与代码
思路展现
理想情况
思路:
既然现在给定了一个x值,需要将小于x的节点排在其余结点之前,且不能改变原来的数据顺序,那么我们此时可以定义两个链表
来分别存储小于x的节点
和大于x的节点
(且原先数据的顺序不变
),最终将用于存储值域小于x的节点的链表的尾部
和用于存储值域小于x的节点的链表的头部
进行合并即可.
合并成功后返回新链表的头节点
举例:
现在假定一个链表为上图所示:5->90->6->89->15->91
,现在x的取值为10
,然后按照我们的思路来说的话,此时应该创建两个链表,一个链表存储的是5->6
,一个链表存储的是90->89->15->91
,
此外仍需注意,因为后期我们要将一个链表的尾部连接到另一个链表的头部,所以我们给每个链表都应该设置有头指针
和尾指针
,那么小于x的链表的头指针
我们定义为bs
,小于x的链表的尾指针
我们定义为be
,大于x的链表的头指针
我们定义为as
,大于x的链表的尾指针
我们定义为ae
.如下图所示:
当然我们每个链表的头指针和尾指针一开始定义的时候都为null
,后期使用cur
变量遍历原先链表的时候,会将符合两个链表插入条件的节点按照遍历顺序依次插入两个链表当中
,当每个链表插入第一个节点后
,头指针和尾指针都指向这第一个节点
,后期再插入的时候,头指针不动,尾指针开始不断向后遍历,直至链表的尾部
.所以bs
和as
分别存储的是两个链表的头节点的地址,be
和ae
分别存储的是两个链表的尾节点的地址.
最后将链表1尾部6
这个节点与链表2首部90
这个节点进行相连,即执行语句be.next=as
,合并后如下图所示:
特殊情况
当然上述是在非常理想的情况下进行的,做这种算法题目是必定要考虑特殊情况
的,下面我们来看有哪些特殊情况吧
特殊情况1
假设一开始我们的链表就是空的话 ,何谈还要分两个链表合并呢,所以一开始我们就要去判断下我们的链表是否为空,判断链表为空的方法是看我们的头指针pHead所指向的节点是否为空,如果为空,说明链表为空,返回null
特殊情况2
在理想状态下我们知道,我们所分开的两个
用于存储节点的链表都是正好不为空
的,那么假设两个链表一个为空
,一个不为空
怎么办呢?
我们知道,我们代码最后执行的语句一定是将小于x的链表的尾部与大于x的链表的头部相连,并返回新链表的头节点
,那么必定会执行be.next=as
这条语句
那么当小于x的链表为空
时,即be=null
,假定此时我们不去判定存储小于x值的链表是否为空的话,be.next
一定会发生空指针异常
,所以此时需要判断小于x的链表是否为空,即bs==null
,如果bs=null
,就直接返回as
,即大于x的链表的头节点
.
那么当大于x的链表为空
时,即as=null
,此时是没有任何影响的,原因是当执行be.next=as的时候,相当于be.next=null
,在大于x的链表为空的情况下,此时新链表的尾节点正是小于x的链表的尾节点
,所以be.next=null逻辑正确无误,所以无需判断大于x的链表为空的情况,只需要判断存储小于x值的节点的链表为空的情况
.
特殊情况3(最容易忽略的点
)
这个情况也是大家最容易忽略的点:
在一开始我们所谈论的理想状态下的节点为5->90->6->89->15->91
那么现在如果我们将91改成9的话,会变成什么样子呢?我们来看下5->90->6->89->15->9
这个示意图:
分开的两个链表(小于10的链表和大于10的链表)分别为:
最后将两个链表合并:
我们可以发现的是,之前理想状态下原链表最后一个节点的值域为91,正好是大于10的,所以当组成一个新的链表的时候,我们的新链表最后一个节点正是原链表最后一个大于10的节点,其next域正好为null,符合链表的标准.
来看我们新的这个链表,当原链表的最后一个节点为9时,此时是小于10的,那么9这个节点就成为了存储小于x值的节点的链表的最后一个节点,其被移动到了我们新链表的中间位置处,当两个链表合并后,新的链表的尾节点变成了值域为15的这个节点,可以看到其next域还存储着值域为9这个节点的地址,按照链表的标准
,值域为15的这个节点的next域应该为null
,所以如果不去处理这种情况,代码是跑不了的,所以最终在返回新合并的链表的头节点之前要对新合并的链表的尾节点的next域进行判断
,如果不为空,应置为空
,这也是为了防止我们刚才所看到的特殊情况的发生而专门制定的.
代码示例
public class Partition {
public ListNode partition(ListNode pHead, int x) {
if(pHead==null){
return null;
}
ListNode bs=null;
ListNode be=null;
ListNode as=null;
ListNode ae=null;
ListNode cur=pHead;
while(cur!=null){
if(cur.val<x){
if(bs==null){
//说明一个节点还没有的情况下
bs=cur;
be=cur;
}else{
//说明头节点已经有了
be.next=cur;
be=be.next;
}
}else{
if(as==null){
说明一个节点还没有的情况下
as=cur;
ae=cur;
}else{
//说明头节点已经有了
ae.next=cur;
ae=ae.next;
}
}
cur=cur.next;
}
if(bs==null){
//bs=null说明第一个段里没有数据
return as;
}
be.next=as;
if(as!=null){
//说明最后一段肯定有数据,所以最后一个节点的next一定为null
//不管它最后一个节点的next域是不是null,都把它写上.
ae.next=null;
}
return bs;
}
}
总结
这道题目做法不难想,但是特殊情况非常的多。尤其特殊情况3
中的要重点关注,这是非常容易忽略的点,需要大家尤其注意。
我们给出的算法:
时间复杂度:O(N
空间复杂度:O(1)