题目描述
给定一链表及一值x
,对链表进行分区,使得所有小于x
的节点均在 大于等于x
节点的之前。
你应当保留节点的初始相对位置。
示例1:
输入: head = [1, 4, 3, 2, 5, 2], x = 3
输出: head = [1, 2, 2, 4, 3, 5]
思路分析
这道题我第一印象就是和插入排序很像,只不过插入排序常规下是对数组进行操作,且插入的时候需要对已经排序后的部分进行数值大小判断,进而确定插入位置。
有了插入排序为基础,我们来区分一下具体差异。首先,这里给定了一个x
,小于x
的需要尽量在一起并放置于大于等于x
这部分 分区的左侧,这就需要结合链表本身的数据结构特点来实现 剥离某个节点并插入另一个位置这个操作;其次,我们可以看到其插入的时候并没有要求按照升序或者降序的要求进行,所以这里只是将剥离的节点放在小值分区的尾节点(这里我们将小于x
的那部分分区称为小值区间)。这里将需要具体化的操作罗列出来并进行分析:
- 剥离某个节点并插入另一个位置;
- 将剥离的节点放在小值分区的尾节点;
我们对这两个问题进行一一解答:
- 对于链表这种数据结构,我们需要在剥离后修复其前置节点与后置节点的关系,所以我们需要寻找被剥离节点的前置节点,换一个角度就是判断谁的后置节点是需要被剥离的,这里我们需要设置一个指针
p
,并判断p.next < x
的关系;当我们确定某个节点满足p.next < x
后,需要确定插入的位置,这里不同于插入排序的地方是,小值区间不排列,但是因为需要保持相对位置,故需要找到小值区间的尾节点(这里我们设置另一个指针,指向小值区间尾节点pivotTail
),并把最新剥离的节点设置为小值区间的新尾节点; - 更新小值区间尾节点
pivotTail
的操作需要将后置节点的前置节点更新成剥离节点,并设置pivotTail.next
为剥离节点,并更新pivotTail
为剥离节点。
步骤罗列
- 初始化哨兵节点
dummyHead
、小值区间尾节点pivotTail
与遍历指针p
; p
遍历链表,搜索满足p.next.val < x
的节点,即剥离节点;- 保存剥离节点的状态信息,并插入指定位置,更新小值区间尾节点
pivotTail
;
解题代码
public static ListNode solution(ListNode head, int x) {
if (head == null || head.next == null) {
return head;
}
// Step1: init pointers
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pivotTail = dummyHead;
ListNode p = dummyHead;
ListNode tmpNode;
/*
Step2:
seek node with value less than x
*/
while (p.next != null) {
if (p.next.val < x) {
//head node
if (p.next == head) {
p = p.next;
pivotTail = head;
} else {
/*Step3
preserve specified node's status
and
insert it to proper position
*/
tmpNode = p.next;
p.next = p.next.next;
tmpNode.next = pivotTail.next;
pivotTail.next = tmpNode;
pivotTail = tmpNode;
}
} else {
p = p.next;
}
}
return dummyHead.next;
}
复杂度分析
时间复杂度:我们对数据对数据遍历了一次,时间复杂度为O(N);
空间复杂度:我们没有借助额外的容器,所以空间复杂度为常量级O(1)。
GitHub源码
完整可运行文件请访问GitHub。