leetcode刷了9题了,一直没有在博客上面记录一下,发现做完一题忘了一题所以今天开始做一道记一道。
使用递归的归并排序
首先这个方法肯定不满足题目要求的常数级空间复杂度,但是还是从这里入手,因为算法太渣了。。。。
链表的排序要比数组排序难,主要是链表的断链还有重新建立连接。递归需要的时间复杂度为递归的深度为logn
递归排序的思想很简单,先把递归的把左面和右面排好序然后在merge
代码分为两个部分
- cut部分
先用fast和slow双指针法找到链表的中点(奇数链表为中点,偶数链表为中点的左面一个)
灵魂画图。。。。。。。。
然后进行断链操作,把两个链表进行递归操作
递归结束条件 : 一个节点 - merge部分
新建一个临时的头结点用于构造新链表然后定义一个H指针指向新链表的最后一个结点,接下来比较P1和P2哪个小就把哪个接上就好了,最后把剩余加到尾巴处就可以了
//链表排序 要求nlogn
public class leetcode148 {
public static class ListNode{
public int val;
public ListNode next;
public ListNode( int val){
this.val = val;
}
}
//排序cut部分
public static ListNode mergeSort(ListNode head){
if(head==null||head.next==null){
return head;
}
ListNode slow = head;
ListNode quick = head.next;
while(quick!=null&&quick.next!=null){
slow = slow.next;
quick = quick.next.next;
}
ListNode tmp = slow.next;
slow.next = null;
// printList(head);
// printList(tmp);
head = mergeSort(head);
tmp = mergeSort(tmp);
return merge(head,tmp);
}
//合并部分
private static ListNode merge(ListNode head, ListNode tmp) {
ListNode p1 = head;
ListNode p2 = tmp;
ListNode tempH = new ListNode(0);
ListNode h = tempH;
while(p1!=null&&p2!=null){
if(p1.val<p2.val){
h.next = p1;
h = p1;
p1=p1.next;
}else{
h.next = p2;
h = p2;
p2=p2.next;
}
}
if(p1!=null){
h.next = p1;
}
if(p2!=null){
h.next = p2;
}
// printList(tempH.next);
return tempH.next;
}
//打印链表
public static void printList(ListNode head){
ListNode p = head;
while(p!=null){
System.out.print(p.val+"->");
p=p.next;
}
System.out.println();
}
public static void main(String[] args) {
ListNode a = new ListNode(9);
a.next= new ListNode(1);
a.next.next = new ListNode(7);
a.next.next.next = new ListNode(3);
a.next.next.next.next = new ListNode(5);
a.next.next.next.next.next = new ListNode(2);
printList(a);
ListNode head = mergeSort(a);
printList(head);
}
}
从底到顶直接排序(非归并排序)
如果要求空间复杂度为O(1)的话就不能用递归来排序了,只能用迭代的方式
先1个1个合并然后2个2个合并,结束条件为合并的个数大于等于链表长度
代码主要是控制两个循环
size循环
- 控制合并的个数size增长,每轮合并size长度的链表。
- size=1 每次循环乘以2 当size<=length时一直继续循环
合并
- tail指针开始指向dummyhead,cur指向第一个节点
- left指针指向左面的链表,right指向右面的链表
- 通过cut操作可以实现断链并返回链表后面的部分,cur指向后面的部分
- 然后merge操作是双路归并,返回的链表可以用tail指向
- 最后移动tail到最后一个结点
- cur==null时代表此轮归并完毕
cut操作
cut操作可以将链表截断前n长度并返回后面的部分
容易出现的错
cut操作可能size大小比链表长度要大,所以要在循环里判断一下
dummyhead的用处:
每轮cur都从新指向头结点,所以定义一个虚拟的头结点,这样很方便就能找到新的结点(开始代码写错了,写的是cur=head,但是head已经被排在后面了)
因为头结点会变化,定义一个虚拟的头结点代替头结点的指针。
/*merge 和 数据结构 在上面有*/
//不用递归的归并排序
public static ListNode mergeSort2(ListNode head){
if(head==null||head.next==null){
return head;
}
//先求链表的长度
int length = 0;
ListNode p = head;
while(p!=null){
p=p.next;
length++;
}
ListNode dummyHead = new ListNode(Integer.MIN_VALUE);
dummyHead.next = head;
//迭代控制每次合并的size大小
for(int size=1;size<=length;size <<=1){
//不能用head头结点 head头结点已经变化到末尾了
ListNode cur = dummyHead.next;
ListNode tail = dummyHead;
//按size大小合并一轮
while(cur!=null){
ListNode left = cur;
ListNode right = cut(left,size);
cur = cut(right,size);
tail.next = merge(left,right);
//tail指针指向最后一个结点可以连接merge完的链表
while(tail.next!=null){
tail=tail.next;
}
}
//printList(dummyHead.next);
}
return dummyHead.next;
}
//切断链表的n个结点 然后返回剩余部分的head
public static ListNode cut(ListNode head,int size){
if(head==null){
return null;
}
ListNode cur = head;
while(--size!=0&&cur.next!=null){
cur = cur.next;
}
ListNode p = cur.next;
cur.next = null;
return p;
}