leetcode-148-排序链表-java

题目及测试

package pid148;
/*   链表排序

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4

示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5


*/
public class main {
	
public static void main(String[] args) {
		
		LinkList a=new LinkList(4);
		a.addLast(2);
		a.addLast(1);
		a.addLast(3);
		a.addLast(5);
		a.printList();
		test(a.first);
		
		LinkList b=new LinkList(-1);
		b.addLast(5);
		b.addLast(2);
		b.addLast(4);
		b.addLast(0);
		b.addLast(3);
		b.printList();
		test(b.first);		

	}
		 
	private static void test(ListNode ito) {
		Solution solution = new Solution();
		ListNode rtn;
		long begin = System.currentTimeMillis();
		System.out.println();
		//开始时打印数组
		
		rtn=solution.sortList(ito);//执行程序
		long end = System.currentTimeMillis();	
		System.out.println("rtn=");
		rtn.printNodeToEnd();
		
		//System.out.println(":rtn" );
		//System.out.print(rtn);
		System.out.println();
		System.out.println("耗时:" + (end - begin) + "ms");
		System.out.println("-------------------");
	}

}

解法1(成功,5ms,较快)

采用自顶向下的归并排序算法,速度O(nlogn),空间O(logn)

通过递归实现链表归并排序,有以下两个环节:

    分割 split环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
        找到中点 now后,执行 now.next = Null将链表切断。
        递归分割时,输入当前链表左端点 head 和中心节点 now的下一个节点 next(因为链表是从 now切断的)。
        split递归终止条件: 当head.next == Null时,说明只有一个节点了,直接返回此节点。
    合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
        双指针法合并,建立辅助ListNode h 作为头部。
        设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
        返回辅助ListNode h 作为头部的下个节点 h.next。
        时间复杂度 O(l + r),l, r 分别代表两个链表长度。
    当题目输入的 head == Null时,直接返回Null。

class Solution {
	public ListNode sortList(ListNode head) {
		if(head==null||head.next==null){
			return head;
		}
	    ListNode head1=head;
	    ListNode head2=splitList(head1);
	    head1=sortList(head1);
	    head2=sortList(head2);
	    ListNode result=mergeTwoList(head1, head2);		
		return result;
    }
	
	/** 分割链表,返回后半段链表的头,前半段的头还是head,尾部的next为null
	 * @param head
	 * @return
	 */
	private ListNode splitList(ListNode head){
		if(head==null||head.next==null){
			return null;
		}
		int length=0;
		ListNode now=head;
		while(now!=null){
			length++;
			now=now.next;
		}
		now=head;
		for(int i=0;i<length/2-1;i++){
			now=now.next;
		}
		ListNode next=now.next;
		now.next=null;
		return next;		
	}
	
	/** 合并两个已排序的链表
	 * @param node1
	 * @param node2
	 * @return
	 */
	private ListNode mergeTwoList(ListNode node1,ListNode node2){
		if(node1==null){
			return node2;
		}
		if(node2==null){
			return node1;
		}
		ListNode first=new ListNode(-1);
		ListNode now=first;
		while(node1!=null&&node2!=null){
			if(node1.val<node2.val){
				now.next=node1;
				now=now.next;
				node1=node1.next;
			}else{
				now.next=node2;
				now=now.next;
				node2=node2.next;
			}
		}
		if(node1==null){
			now.next=node2;
		}else{
			now.next=node1;
		}
		return first.next;
	}
}

解法2(别人的)

采用自底向上的归并排序算法,速度O(nlogn),空间O(1)

对于非递归的归并排序,需要使用迭代的方式替换cut环节:

    我们知道,cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
    每一轮合并merge操作针对的单元都有固定长度intv,例如:
        第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。
        第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
        以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
    根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
        当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
        当intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
        当intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。

模拟上述的多轮排序合并:

    统计链表长度length,用于通过判断intv < length判定是否完成排序;
    额外声明一个节点res,作为头部后面接整个链表,用于:
        intv *= 2即切换到下一轮合并时,可通过res.next找到链表头部h;
        执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。
    在每轮intv下的合并流程:
        根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:
            在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;
            若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。
        合并长度为c1, c2的h1, h2链表,其中:
            合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)
            合并单元尾部同时也作为下次合并的辅助头部pre。
        当h == None,代表此轮intv合并完成,跳出。
    每轮合并完成后将单元长度×2,切换到下轮合并:intv *= 2。

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        ListNode dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode prev = dummyHead, curr = dummyHead.next;
            while (curr != null) {
                ListNode head1 = curr;
                for (int i = 1; i < subLength && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode head2 = curr.next;
                curr.next = null;
                curr = head2;
                for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode next = null;
                if (curr != null) {
                    next = curr.next;
                    curr.next = null;
                }
                ListNode merged = merge(head1, head2);
                prev.next = merged;
                while (prev.next != null) {
                    prev = prev.next;
                }
                curr = next;
            }
        }
        return dummyHead.next;
    }

    public ListNode merge(ListNode head1, ListNode head2) {
        ListNode dummyHead = new ListNode(0);
        ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
        while (temp1 != null && temp2 != null) {
            if (temp1.val <= temp2.val) {
                temp.next = temp1;
                temp1 = temp1.next;
            } else {
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        if (temp1 != null) {
            temp.next = temp1;
        } else if (temp2 != null) {
            temp.next = temp2;
        }
        return dummyHead.next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值