Partition List

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;

    }

}

 

你可能感兴趣的:(partition)