给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
题目分析:本题让我们把原来的链表进行分隔,小于x的在前,大于等于x的在后。并且,元素之间还需要保持原有的相对顺序。在下面的图例中,我们可以看到原有元素的顺序。分隔完后,在原来索引位置5处的元素2依然排在索引位置为3处的元素2之后。
在思考这道题如何解的时候,我们先回忆一下我们在算法课中也接触过类似的partition方法,这种方法在quick sort(快速排序)中也出现过。快速排序采用分治的思想,通过某一分界值将数组分成左右两部分,将大于等于分界值的数据集中到右侧,将小于分界值的数据集中到左侧。然后分别对左侧右侧进行上述处理,直到每一侧都排好序,那么整体也就排好序了。我们看下面的这个例子,对原数组我们选择最后一个元素作为分界值,然后左侧都是小于70的元素,右侧都是大于70的元素。然后对于左侧和右侧,我们选择他们中的最后一个元素作为分界值继续进行划分,一直到最后每一侧都排好序(即这一侧只含有一个元素或者为空),该算法停止。该算法的时间复杂度为O(nlogn)。
对应的C++参考代码如下
int partition(vector<int> &input, int low, int high) {
int pivot = input[high];
int i = low - 1;
for (int j = low; j <= high - 1; j++) {
if (input[j] < pivot) {
++i;
swap(input[i], input[j]);
}
}
swap(input[i + 1], input[high]);
return i + 1;
}
void quickSort(vector<int> &input, int low, int high) {
if (low < high) {
int index = partition(input, low, high);
quickSort(input, 0, index - 1);
quickSort(input, index + 1, high);
}
}
回到本题中,既然需要将链表分隔为两部分,那么我们可以设置两个dummy节点分别保存链表的一部分,dummy1作为小于x的节点的链表的头结点,dummy2作为大于等于x的节点的链表的头结点,如果当前节点的值小于x,我们就将当前节点放到dummy1链表的末尾。如果当前节点的值大于等于x,我们就将其插入dummy2链表的末尾。这样我们就将原链表划分成了两部分,并且保证了相对顺序。最后链接成的链表结果如下图显示,我们的算法时间复杂度为O(n)。下面分别给出不同语言的代码实现。
C++代码
ListNode* partition(ListNode* head, int x) {
if (head == NULL || head->next == NULL) return head;
ListNode* dummy1 = new ListNode(0);
ListNode* dummy2 = new ListNode(1);
ListNode *tail1 = dummy1, *tail2 = dummy2;
ListNode* curr = head;
while (curr != NULL) {
if (curr->val < x) {
tail1->next = curr;
tail1 = tail1->next;
} else {
tail2->next = curr;
tail2 = tail2->next;
}
curr = curr->next;
}
if (tail1 == NULL) return tail2;
if (tail2 == NULL) return tail1;
tail2->next = NULL;
tail1->next = dummy2->next;
return dummy1->next;
}
Java代码
public ListNode partition(ListNode head, int x) {
if (head == null || head.next == null) return head;
ListNode dummy1 = new ListNode(0);
ListNode dummy2 = new ListNode(0);
ListNode smallTail = dummy1, largeTail = dummy2;
ListNode curr = head;
while (curr != null) {
if (curr.val < x) {
smallTail.next = curr;
smallTail = smallTail.next;
} else {
largeTail.next = curr;
largeTail = largeTail.next;
}
curr = curr.next;
}
smallTail.next = dummy2.next;
largeTail.next = null;
return dummy1.next;
}
C#代码
public ListNode Partition(ListNode head, int x) {
if (head == null || head.next == null)
{
return head;
}
ListNode dummy1 = new ListNode(0);
ListNode dummy2 = new ListNode(0);
ListNode smallTail = dummy1, largeTail = dummy2;
ListNode curr = head;
while (curr != null)
{
if (curr.val < x)
{
smallTail.next = curr;
smallTail = smallTail.next;
} else
{
largeTail.next = curr;
largeTail = largeTail.next;
}
curr = curr.next;
}
smallTail.next = dummy2.next;
largeTail.next = null;
return dummy1.next;
}
Golang代码
func partition(head *ListNode, x int) *ListNode {
if head == nil || head.Next == nil {
return head
}
dummy1 := new(ListNode)
dummy2 := new(ListNode)
smallTail := dummy1
largeTail := dummy2
curr := head
for curr != nil {
if curr.Val < x {
smallTail.Next = curr
smallTail = smallTail.Next
} else {
largeTail.Next = curr
largeTail = largeTail.Next
}
curr = curr.Next
}
smallTail.Next = dummy2.Next
largeTail.Next = nil
return dummy1.Next
}
Python3代码
def partition(self, head: ListNode, x: int) -> ListNode:
if head == None or head.next == None:
return head
dummy1 = ListNode(0)
dummy2 = ListNode(0)
smallTail = dummy1
largeTail = dummy2
curr = head
while curr != None:
if curr.val < x:
smallTail.next = curr
smallTail = smallTail.next
else:
largeTail.next = curr
largeTail = largeTail.next
curr = curr.next
smallTail.next = dummy2.next
largeTail.next = None
return dummy1.next
由本题可见,一些经典的算法我们还是要学透吃透,这样在碰到类似问题或者问题变种的时候才能将经典算法应用过来,快速解决问题。