徒手挖地球十八周目
NO.23 合并K个排序链表 困难
思路一:逐一两两合并 将NO.21合并两个有序链表中的方法进行k-1次即可。
public ListNode mergeKLists(ListNode[] lists) {
if (lists==null||lists.length==0)return null;
if (lists.length<2)return lists[0];
ListNode dummy=new ListNode(-1);
dummy.next=lists[0];
for (int i=1;i<lists.length;i++){
ListNode head=dummy,p=dummy.next,q=lists[i];
while (q!=null&&p!=null){
if (q.val< p.val){
head.next=q;
q=q.next;
}else {
head.next=p;
p=p.next;
}
head=head.next;
}
if (q!=null)head.next=q;
if (p!=null)head.next=p;
}
return dummy.next;
}
时间复杂度:O(Nk) N是节点总数,k是链表数
思路二:分治法优化两两合并 每次对折合并,0号链表和length-1号链表合并保存到0、1号链表和length-2号链表合并保存到1。。。第一轮合并后,将0~k/2再次对折两两合并。。。以此类推,最后0号链表就是最终结果。
public ListNode mergeKLists(ListNode[] lists) {
int len = lists.length;
if (lists==null|| len ==0)return null;
while (len>1){
for (int i=0;i<len/2;i++){
//中心对称,两两合并
lists[i]=mergeTwoList(lists[i],lists[len-1-i]);
}
len=(len+1)/2;
}
return lists[0];
}
//合并两个链表
public ListNode mergeTwoList(ListNode l1,ListNode l2){
ListNode dummy=new ListNode(-1);
ListNode head=dummy,p=l1,q=l2;
while (p!=null&&q!=null){
if (p.val<q.val){
head.next=p;
p=p.next;
}else {
head.next=q;
q=q.next;
}
head=head.next;
}
if (q!=null)head.next=q;
if (p!=null)head.next=p;
return dummy.next;
}
时间复杂度:O(Nlogk) N是节点总数,每次对折合并所有节点都参与了,一共对折合并了logk次。
NO.25 K个一组翻转链表 困难
思路一:迭代实现 和徒手挖地球九周目中NO.24两两交换链表中的节点的迭代法思路一样,不过NO.24题中的k是2而已。
- 哑节点dummy。pre指向待翻转子链表的前驱,end指向待翻转子链表的尾节点。然后,start指向待翻转子链表的头节点,next指向待翻转子链表的后继。最后断开待翻转子链表和剩余链表,翻转第一组。
- 反转完成之后,将start节点和next节点连接。移动pre指向start节点,end指向pre节点,检查end.next不为空,所以向后移动end到下一组待翻转子链表的尾节点,start指向待翻转子链表的头节点,next指向待翻转子链表的后继。翻转第二组。
- 第二组翻转完成,将start节点和next节点连接。移动pre指向start节点,end指向pre节点,检查end.next不为空,所以向后移动end,但是剩余节点不足k个。所以翻转全部,返回dummy.next。
这里还有一个问题就是如何翻转子链表reverse(head)?用上述第一组子链表为例:
curr指向当前节点,pre指向curr之前节点,next指向curr之后节点,翻转过程比较简单,直接看图。
public ListNode reverseKGroup(ListNode head, int k) {
if (k==1)return head;
//初始化哑节点、pre、end
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode pre=dummy,end=dummy;
while (end.next!=null){
//移动end指向待翻转子链表的尾部,如果剩余节点不足k个,则翻转完成返回head
for (int i = 0; i < k&& end!=null; i++) end=end.next;
if (end==null)break;
//start指向待翻转子链表头节点,next指向未翻转部分的头节点
ListNode start=pre.next,next=end.next;
end.next=null;
pre.next=reverse(start);
//连接完成翻转部分和未翻转部分
start.next=next;
//移动pre和end
pre=start;
end=pre;
}
return dummy.next;
}
//翻转子链表
private ListNode reverse(ListNode head) {
ListNode pre=null,curr=head;
while (curr!=null){
ListNode next=curr.next;
curr.next=pre;
pre=curr;
curr=next;
}
return pre;
}
时间复杂度:O(nk) n是节点总数。