题目描述
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
解题思路
题目解读
首先我们先对题目进行解读,第一个要求就是:使得所有小于x的节点都在大于或等于x的节点之前,那么拿实例来说,就是节点1,2,2都在节点4,3,5之前,一开始我以为结果应该为1->2->2->3->4->5,那这不是与它的结果不相符吗?如果没有注意到第二个要求,那么确实会产生这样的疑问,那么我们来看他的第二个要求:你应当保留两个分区中每个节点的初始位置,我们怎么理解呢?首先我们应当知道,要求所指的两个分区就是比x小的分区和比x大的分区,那么保留每个分区中每个节点的初始位置就是指链表中每个节点的相对位置不变,举个例子来说,节点4和节点3都是属于大于x的分区,那么在原先的链表中4在3的前面,那么在分区中4也应该在3的前面,故结果中4在3的前面就有了解释,因为它题目并没有要求分区中也要按照大小排序,现在,我们题目已经解读完毕
解题想法
一开始我是打算这样入手的:从头节点的下一个节点开始遍历一遍链表,把比x小的节点插入到头节点的后面,当链表遍历完毕,在判断头节点的值和x的大小关系,如果比x值大,那么就把头节点插入到小分区(比x值小的分区)的末尾,将小分区的第一个节点作为头节点;如果比x值小,那么直接返回就行,拿示例来说,那么遍历完之后就是1->2->2->4->3->5,然后再判断头节点1和x的大小关系,1比3小,那么直接返回,代码如下:
/*ListNode* partition(ListNode* head, int x) {
if (head == NULL)
return NULL;
if (head->next == NULL)
return head;
if (head->next->next == NULL) {
if (head->val < x )
return head;
else if (head->val >= x && head->next->val >= x)
return head;
else if (head->val >= x && head->next->val < x) {
ListNode* node = head->next;
head->next = NULL;
node->next = head;
head = node;
}
}
ListNode* node = head;
ListNode* start = head;
ListNode*end; //保存下一个位置
while (node->next != NULL) {
if (node->next->val < x) {
end = node->next; //保留下一个节点
node->next = end->next;
//将小于x的节点放到小于x的分区的末尾
end->next = start->next;
start->next = end;
start = end;
if (node == NULL)
break;
}
else
node = node->next;
}
if (head->val >= x) {
node = head->next;
end = start->next;
start->next = head;
head->next = end;
head = node;
}
return head;
}*/
上面的代码对于实例可以通过,但是对于一些特殊情况(1 1)却不能得到正确的答案,想了半天,改了半天还是不能很好的解决,所以放弃这种想法
正确的一种思路
看了一些解答和官方的解释,一种解决思路就是运用哑节点,那么什么是哑节点呢?我自己的理解就是:哑节点是我们自己设置的一个节点,它位于分区的开头,作为一种标识标志。那么按照理解,哑节点和头节点是有点类似,他们两者之间有什么区别呢?我自己个人的理解就是:首先哑节点他是一个数据节点,而头节点是一个指针,我们在创建链表时,通常都是先申请一个链表类型的空间(通常是使用malloc(sizeof(struct ListNode))),然后返回一个指针,那么这个头节点就是这个指针,哑节点就是ListNode这个数据变量。
那么为什么要设置哑节点呢?我认为就是便于后面的连接两个分区和返回值,还省略了很多的条件判断。代码如下:
ListNode* partition(ListNode* head, int x) {
ListNode empty1(0), empty2(0);
ListNode* less_head = &empty1;
ListNode* more_head = &empty2;
while (head != NULL) {
if (head->val < x) {
less_head->next = head;
less_head = head;
}
else {
more_head->next = head;
more_head = head;
}
head = head->next;
}
more_head->next = NULL;
less_head->next = empty2.next;
return empty1.next;
}
首先我们先定义了两个哑节点empty1和empty2,然后再定义两个指针指向他们,这样两个哑节点就成为了两个分区的标识(那么为什么不直接定义两个ListNode指针作为两个分区的标识呢,我认为主要是因为哑节点是实实在在的数据,而定义ListNode指针不便于后面的连接和返回),后面的就是遍历和连接了