题目
注意验证:链表为空的情况!!
你应当 保留 两个分区中每个节点的初始相对位置:算法要具备稳定性
解题
刚拿到这道题时,马上想到快速排序里面的 partition 算法,但是这题要求没有那么严格:大于等于 x 的元素只要在右半部分即可,没有要求 x 的左边全是小于等于 x 的数,右边全是大于等于 x 的数。
解法一
# python
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
BeforeNode = BeforeListHead = ListNode(0) # 哨兵节点
AfterNode = AfterListHead = ListNode(0) # 哨兵节点
CurNode = head
while CurNode:
NewNode = ListNode(CurNode.val)
if CurNode.val < x:
BeforeNode.next = NewNode
BeforeNode = BeforeNode.next
else:
AfterNode.next = NewNode
AfterNode = AfterNode.next
CurNode = CurNode.next
BeforeNode.next = AfterListHead.next
return BeforeListHead.next
上面的解答用了【哨兵节点】,第一次看到哨兵节点的介绍时还不以为然,现在就两个字“真香”。即便 AfterListHead 的链表没有插入元素,也至少有一个哨兵节点,它的 next 指向 None。
还有一点要注意的是:这里用了四个变量来跟踪两个链表,原因是 BeforeNode 和 AfterNode 会被挪动,需要另外两个变量来记录链表的头节点,否则两个链表没法相连,也找不到该返回的节点。
看了官方的解法后发现,因为这里没有去改变原链表,所以不用每次都 NewNode = ListNode(CurNode.val),可以直接指向原链表节点,但是有一个注意事项:最后要把 AfterNode.next 指向 None。
# python
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
BeforeNode = BeforeListHead = ListNode(0)
AfterNode = AfterListHead = ListNode(0)
while head:
if head.val >= x:
AfterNode.next = head
AfterNode = AfterNode.next
else:
BeforeNode.next = head
BeforeNode = BeforeNode.next
head = head.next
AfterNode.next = None # ⚠️ attention!!!
BeforeNode.next = AfterListHead.next
return BeforeListHead.next
空间上,因为指向的是原链表节点,没有用额外的空间来存储链表。
// javascript
var partition = function(head, x) {
let small = new ListNode(0), large = new ListNode(0);
const smallHead = small, largeHead = large;
while (head !== null) {
if (head.val < x){
small.next = head;
small = small.next;
}
else{
large.next = head;
large = large.next;
}
head = head.next;
}
large.next = null;
small.next = largeHead.next;
return smallHead.next;
};
解法二
思路是先判断链表是否为空,如果是空的,则返回空链表;否则将两个代表链表头尾的变量指向原链表的第一个元素,跳过第一个节点去遍历后面的节点(head = head.next;)。如果 < x,因为不能改变原链表,所以 new 一个 node,值为 head.val,next 指向原头节点,并更新头节点;否则直接将尾节点指向原链表节点,并更新尾节点。
// javascript
var partition = function(head, x) {
if (head === null) return head; // [], x=3
let headNode = head, tailNode = head;
head = head.next;
while (head !== null) {
if (head.val < x){
let newNode = new ListNode(head.val);
newNode.next = headNode;
headNode = newNode;
}
else{
tailNode.next = head;
tailNode = tailNode.next;
}
head = head.next;
}
tailNode.next = null;
return headNode;
};
官方比较狠的是,用一个变量去存储 head.next,这样就不用分配空间来存储新节点啦,原 head.next 也不会随着 head.next 的更新而丢失。
下面的代码里我注释掉了一段,起初我认为既然头尾变量已经指向第一个节点,那么该跳过第一个节点往后去遍历。但其实跟着代码跑一遍的话会发现,因为while循环并不像for循环一样在for语句里有指针的移动,实际上所有指针都定在原位,所以处理第一个节点后,头尾变量仍指着原链表第一个节点。
// javascript
var partition = function(head, x) {
let headNode = head, tailNode = head;
while (head !== null) {
let next = head.next; // 保存下一个node
/*if (headNode === head){
continue;
}*/
if (head.val < x){
head.next = headNode; // 不怕丢,已经存起来啦
headNode = head;
}
else{
tailNode.next = head;
tailNode = tailNode.next;
}
head = next; // 拿回下一个node
}
if (tailNode !== null){ // [], x=3
tailNode.next = null;
}
return headNode;
};
// or
var partition = function(head, x) {
if (head === null) return head;
let headNode = head, tailNode = head;
head = head.next;
while (head !== null) {
let next = head.next;
if (head.val < x) {
head.next = headNode;
headNode = head;
}
else {
tailNode.next = head;
tailNode = tailNode.next;
}
head = next;
}
tailNode.next = null;
return headNode;
};
解法三
我第一次想的办法其实是,一旦看到 >= x 的节点,把该节点挪到链表尾端。但上面两种解法优化后 空间复杂度 都是 O ( 1 ) O(1) O(1),如果要去链表尾部插节点,再将原节点删除,势必要用额外的空间。从这个角度来想,解法三没能借助链表的优势,从一开始便输了。
# python
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
SentinalNode = ListNode(0)
SentinalNode.next = head
i_Node = j_Node = SentinalNode
while j_Node.next:
j_Node = j_Node.next
while i_Node.next != j_Node and i_Node.next: # [], x=3
if i_Node.next.val >= x:
NewNode = ListNode(i_Node.next.val)
NewNode.next = j_Node.next
j_Node.next = NewNode
i_Node.next = i_Node.next.next
else:
i_Node = i_Node.next
return SentinalNode.next
从这道题可以看出来,自己对链表的认识还是不太深刻,总喜欢把数组的处理方法带过来,还是要勤练、多思考。