楼主昨天看到阿里的一道算法题是只使用O(1)的额外空间,合并两个无序链表。乍一看有点奇怪,一般都是双指针算法合并两个有序链表,后面思考过后除了不能像数组能够随机访问链表的每个元素外合并两个有序链表实际上就是先对每个链表进行排序,然后将两个有序链表合并起来。也就是分为两步。
- 对两个链表分别排序
- 将两个有序链表合并起来
然后楼主看了一下网上的其他帖子,大部分都是使用选择啊、冒泡啊这种O(n^2)的算法。这效率实在太低了。尽管链表确实不像数组那样访问元素方便,但是通过快慢指针我们页能很快的就找到我们链表中指定的某个元素。而且链表天生就支持类似归并排序的操作,因此下面给出一种类似于归并排序O(nlogn)的排序算法;
public class 合并两个无序链表 {
static Node l1;
static Node l2;
static class Node {
int val;
Node next;
public Node() {
}
public Node(int val) {
this.val = val;
}
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
}
private static void init() {
Node vir1 = new Node();
Node vir2 = new Node();
Node p = vir1;
Node q = vir2;
Random random = new Random();
for (int i = 0; i < 3; i++) {
Node np = new Node(random.nextInt(30));
p.next = np;
p = np;
Node nq = new Node(random.nextInt(30));
q.next = nq;
q = nq;
}
l1 = vir1.next;
l2 = vir2.next;
}
private static void show(Node l1,Node l2){
Node p = l1;
Node q = l2;
while (p != null){
System.out.print(p.val+" ");
p = p.next;
}
System.out.println();
while (q != null){
System.out.print(q.val + " ");
q = q.next;
}
System.out.println();
}
private static Node merge(Node h1, Node h2) {
// System.out.println("merge");
//合并两个有序链表
Node vir = new Node();
Node sc =vir;
Node p = h1;
Node q = h2;
while (p != null && q != null){
if (p.val <= q.val){
Node pn = p.next;
sc.next = p;
p = pn;
}else {
Node qn = q.next;
sc.next = q;
q = qn;
}
sc = sc.next;
// sc.next = null;
}
if (p != null) sc.next = p;
if (q != null) sc.next = q;
return vir.next;
}
private static Node merge_sort(Node head) {
// System.out.println("merge_sort");
if (head == null || head.next == null) return head;
Node fast = head;
Node low = new Node(0,head);
// 1 2
while (fast != null && fast.next != null){
fast = fast.next.next;
low = low.next;
}
//low所在的位置是右边头节点的前一个
Node rhead = low.next;
low.next = null;//断开连接
Node lh = merge_sort(head);
Node rh = merge_sort(rhead);
Node merge = merge(lh, rh);
return merge;
}
public static void main(String[] args) {
init();
show(l1,l2);
l1 = merge_sort(l1);
l2 = merge_sort(l2);
Node res = merge(l1,l2);
show(res,null);
}
}
上述Node为链表节点、init()方法是对链表进行初始化,生成测试数据(因为楼主找了半天也没找到一个OJ上有这道题),show()是打印链表的内容。剩下就是核心方法merge_sort()和merge()方法。
merge_sort()实际上就是通过快慢指针,快速定位到链表的中点、然后对链表进行分割,分为两个小链表进行排序,最终当链表为一个节点的时候则问题解决,这是分治的思路。
merge()没什么好说的就是合并两个有序链表.
然后我们来看看算法时间复杂度:对于每个merge_sort的方法,快慢指针需要O(n)的时间复杂度,合并两个有序链表也是O(n)的复杂度,然后最多调用logn层的merge_sort,所以最终的算法时间复杂度为O(nlogn)。