题目:
将单向链表按某值划分为左边小、中间相等、右边大的形式,并且调整后三个部分中节点的相对位置和调整前是一样的。要求时间复杂度为O(N),额外空间复杂度为O(1)
分析:
方法1: 采用数组储存链表中的节点,然后采取快排中的Partition操作将数组进行排序为三个部分。但是该方法不稳定,无法满足相对位置不变,且额外空间复杂度为O(N)
方法2:采取有限变量,形成三个链表
1.对三个部分分别设置头指针和尾指针
2.根据节点的大小对三个部分链表进行扩充
3.将三个链表的头和尾进行相连
代码实现:
public class SmallerEqualBigger {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
// 方法1:采用数组储存链表中的节点,在数组中进行Partition操作
public static Node listPartition1(Node head, int pivot) {
if (head == null) {
return head;
}
Node cur = head;
int length_list = 0; // 记录链表长度
while (cur != null) {
length_list++;
cur = cur.next;
}
Node[] nodeArr = new Node[length_list];
cur = head;
for (int i = 0; i < length_list; i++) {
nodeArr[i] = cur;
cur = cur.next;
}
arrPartition(nodeArr, pivot);
// 将数组重新转换为链表
for (int i = 1; i < nodeArr.length; i++) {
nodeArr[i - 1].next = nodeArr[i];
}
nodeArr[nodeArr.length - 1].next = null;
return nodeArr[0];
}
// 将数组根据划分值分为三个部分,参考快排部分有详细讲解
public static void arrPartition(Node[] nodeArr, int pivot) {
int small = -1; // 小于区的右边界
int big = nodeArr.length; // 大于区的左边界
int index = 0; // 表示当前数的位置
while (index != big) {
if (nodeArr[index].value < pivot) { //当前数 < 划分值
swap(nodeArr, ++small, index++);
} else if (nodeArr[index].value == pivot) {
index++;
} else {
swap(nodeArr, --big, index); // 当前数 > 划分值
}
}
}
public static void swap(Node[] nodeArr, int a, int b) {
Node tmp = nodeArr[a];
nodeArr[a] = nodeArr[b];
nodeArr[b] = tmp;
}
// 方法2:采取有限变量
public static Node listPartition2(Node head, int pivot) {
Node sH = null; // small head
Node sT = null; // small tail
Node eH = null; // equal head
Node eT = null; // equal tail
Node bH = null; // big head
Node bT = null; // big tail
Node next = null; // 用于保存下一个节点
while (head != null) {
next = head.next;
head.next = null;
if (head.value < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head;
bT = head;
}
}
head = next; //继续往下遍历
}
// 将小于部分的链表和等于部分的链表进行相连
if (sT != null) { //当sT==null的时候说明小于部分的链表为空
sT.next = eH;
eT = eT == null ? sT : eT; // 谁去连接大于区域的头节点,谁就变为eT
}
// 将三个部分链表全部相连
if (eT != null) { //当eT==null的时候说明等于部分的链表为空
eT.next = bH;
}
// 最后返回头节点需要判断返回三个部分中第一个头节点不为空的节点
return sH != null ? sH : eH != null ? eH : bH;
}
}
tips:
在方法2中的第三步对三个部分进行相连时,需要注意不同部分的链表是否为空。不能简单直接将上个部分的尾节点直接连到下个部分的头节点