https://oj.leetcode.com/problems/partition-list/
Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.
You should preserve the original relative order of the nodes in each of the two partitions.
For example,
Given 1->4->3->2->5->2
and x = 3,
return 1->2->2->4->3->5
.
解题思路:
这个题目不算复杂, 但我在其中的一个子问题上卡了很长时间,只能说是代码太不熟悉。
从宏观上思考题目,遇到比x小的节点(1)可直接跳过不管。遇到>=x的节点,就把他们都[4,3]往后移动,移到下一个比x小(2)的节点,再把这个小于x的节点(2)放到之前大于x区域的第一个位置(4)。等等,这么说好像有点问题,其实是反过来了。为什么?
其实是,遇到>=x的节点不动,但是记录一个位置,就是这个连续都是>=x数字的区域的第一个位置。一旦遇到<x的节点,那么这个区域就清晰了,里面所有元素都>=x。于是,将这个区域整体后移一个,再将当前<x的这个节点放到这个区域的第一个。类似于将区域的最后一个元素放到首元素。这样,原来元素的顺序也不会打乱,是稳定的变化。
同时,把记录这个>=x区域的开始位置后移一个,因为他们都只后移了一位。
然后游标往后,继续遍历整个链表。
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode partition(ListNode head, int x) { ListNode swapNode = head; ListNode traverseNode = head; ListNode returnNode = head; while(traverseNode != null){ if(traverseNode.val >= x){ //do nothing } //考虑例子1->4->3->2->5->2,traverse到2,swapNode在4 if(traverseNode.val < x){ //留着一个备份,因为下面swapNode要往后移动,一直到traverseNode ListNode tempNode = swapNode; //给traverseNode的值留一个备份,后面和swapNode后节点交换 int temp = traverseNode.val; //从[swapNode, traverseNode)每个节点往后移一个 //即变成1->4->4->3->5->2 int pre = swapNode.val; while(swapNode != traverseNode){ int tempValue = swapNode.next.val; swapNode.next.val = pre; pre = tempValue; swapNode = swapNode.next; } //将前面存的最后一个值,2,赋予第一个节点4 //即变成1->2->4->3->5->2 tempNode.val = temp; //swapNode从原先的位置往后移动一个 swapNode = tempNode.next; } // if(swapNode != null && swapNode.val < x){ // swapNode = swapNode.next; // } traverseNode = traverseNode.next; } return returnNode; } }
这个逻辑应该来讲还是比较清晰的,但是在整个区域后移一位的时候,由于循环内需要两个临时变量,和单纯的变量交换还是有很大不同,在这个子问题上,我考虑了很久,基本功弱的缺点毕现。
再想想,一个区域内,所有元素按顺序都后移一位,难道不是用链表操作更简便吗?于是开始写代码。
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode partition(ListNode head, int x) { ListNode traverseNode = head; ListNode returnNode = head; ListNode preTraverseNode = new ListNode(0); preTraverseNode.next = head; ListNode swapNode = preTraverseNode; while(traverseNode != null && preTraverseNode != null){ if(traverseNode.val >= x){ //do nothing } // 必须判断traverseNode != swapNode,否则引起环,死循环 if(traverseNode.val < x && traverseNode != swapNode){ // ListNode next = traverseNode.next; //返回的node不再是原head,而是被甩到前面去较小的那个 if(swapNode.next == returnNode){ returnNode = traverseNode; } preTraverseNode.next = traverseNode.next; traverseNode.next = swapNode.next; swapNode.next = traverseNode; // traverseNode = next; traverseNode = preTraverseNode.next; swapNode = swapNode.next; continue; //traverseNode与pre已经到位,无需再往后移动 } if(traverseNode != null && preTraverseNode != null){ preTraverseNode = preTraverseNode.next; traverseNode = traverseNode.next; } } return returnNode; } }
在实际操作中发现,需要同时取得swapNode和traverseNode的前驱节点。链表操作有个特点,快了,你就再也回不去了,因为它是单向的,如果慢了,你可以一直node.next.next...往下取值。
这里我将swapNode的初始值设置在了一个dummy的节点上,用它指向head,所有swapNode就始终比上题都往前一位,即便这样,还是很容易出错,上面正确的代码我又花了半天时间才写出,错了大概十几个版本吧。
事实上,traverseNode也可以放在这个dummy上,这样就不需要那个preTraverseNode了,原来的preTraverseNode就是现在的traverseNode,原来的traverseNode就是现在的traverseNode.next。中间只需要一个获取preTraverseNode的下一个节点保存preTranverseNode的下一个节点即可,代码如下。
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode partition(ListNode head, int x) { // ListNode traverseNode = head; ListNode returnNode = head; ListNode preTraverseNode = new ListNode(0); preTraverseNode.next = head; ListNode swapNode = preTraverseNode; while(preTraverseNode.next != null){ if(preTraverseNode.next.val >= x){ //do nothing } // 必须判断preTraverseNode.next != swapNode,否则引起环,死循环 if(preTraverseNode.next.val < x && preTraverseNode.next != swapNode){ //返回的node不再是原head,而是被甩到前面去较小的那个 if(swapNode.next == returnNode){ returnNode = preTraverseNode.next; } //获取preTraverseNode的下一个节点,不因为下面就要改变 ListNode nextNode = preTraverseNode.next; preTraverseNode.next = preTraverseNode.next.next; nextNode.next = swapNode.next; swapNode.next = nextNode; swapNode = swapNode.next; continue; //preTraverseNode.next已经到位,无需再往后移动 } if(preTraverseNode.next != null){ preTraverseNode = preTraverseNode.next; // preTraverseNode.next = preTraverseNode.next.next; } } return returnNode; } }
以上均为in place的解法,还有一种解法,遍历一次链表,将<x和>=x的值分别加入一个队列,再重新建立一个新的链表,也可以,不过不是in place。
update 2015/05/19:
二刷
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ public class Solution { public ListNode partition(ListNode head, int x) { if(head == null || head.next == null) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; ListNode cur = dummy; ListNode slow = dummy; while(cur.next != null) { if(cur.next.val < x) { ListNode next = cur.next; cur.next = cur.next.next; ListNode next1 = slow.next; slow.next = next; next.next = next1; slow = slow.next; cur = next; } else { cur = cur.next; } } return dummy.next; } }