LeetCode-21-合并两个有序链表
in LeetCode with 0 comment

LeetCode-21-合并两个有序链表

in LeetCode with 0 comment

原题地址:合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

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

准备工作

定义链表结点:

function ListNode(val) {
    this.val = val;
    this.next = null;
}

数组转链表,方便测试数据的输入:

function arrayToList(array) {
    let node = null;
    for (let i = array.length - 1; i >= 0; i --) {
        let newNode = new ListNode(array[i]);
        newNode.next = node;
        node = newNode;
    }
    return node;
}

链表转字符串,方便测试结果的显示:

function listToString(node) {
    return node ? `${node.val}${node.next ? ` -> ${listToString(node.next)}` : '' }` : null;
}

递归算法

链表的合并可以比较容易写成递归的形式,如果不考虑边界情况,合并两个链表只需要将两个表头中较小的一个作为新的表头,剩下的部分递归合并即可:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
const mergeTwoLists1 = function(l1, l2) {
    // 边界情况,两个链表中有一个为空,则返回另一个链表
    if (!l1) {
        return l2;
    }
    if (!l2) {
        return l1;
    }
    // 都不为空,则比较两个表头的值,将较小的作为新的表头,其它的结点再递归合并
    if (l1.val < l2.val) {
        l1.next = mergeTwoLists1(l1.next, l2);
        return l1;
    }
    l2.next = mergeTwoLists1(l1, l2.next);
    return l2;
};

测试:

let start = new Date();
const test = mergeTwoLists1;
console.log(listToString(test(arrayToList([1, 2, 4]), arrayToList([1, 3, 4])))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4
console.log(new Date().getTime() - start.getTime()); // 3

时间复杂度: 每次递归会合并两个链表中的一个表头,所以时间复杂度为O(m+n),m和n分别为两个链表的长度
空间复杂度: 最差情况下链表的每个结点都递归一次,共有m+n个栈,所有空间复杂度为O(m+n)

迭代

上面的递归的效率不太高,空间占用也很严重,我们可以改写成非递归的形式。

定义一个新链表,新链表的尾结点始终指向两个链表头结点中值较小的一个,并将该链表的头结点后移,这样就可以将两个链表合并起来了:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
const mergeTwoLists2 = function(l1, l2) {
    let preHead = new ListNode(null); // 定义一个头结点之前的结点,方便最后的输出
    let last = preHead; // 新链表的尾结点
    while (l1 && l2) {
        // 两链表都不为空时,尾结点的next指向值较小的结点,同时将该链表的指针后移
        if (l1.val < l2.val) {
            last.next = l1;
            l1 = l1.next;
        } else {
            last.next = l2;
            l2 = l2.next;
        }
        // 尾结点后移
        last = last.next;
    }
    // 尾结点指向剩下不为空的结点
    last.next = l1 || l2;
    return preHead.next;
};

测试:

let start = new Date();
const test = mergeTwoLists2;
console.log(listToString(test(arrayToList([1, 2, 4]), arrayToList([1, 3, 4])))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4
console.log(new Date().getTime() - start.getTime()); // 2

时间复杂度: 最差情况下两个链表中每个结点都需要遍历一次,所以时间复杂度与递归相同,为O(m+n)
空间复杂度: 只需要固定大小的额外空间,空间复杂度为O(1)