返回

揭秘合并两个有序链表的妙技:手撕版算法详解!

后端

众所周知,链表是一种常见的数据结构,在实际应用中随处可见。合并两个有序链表是LeetCode和Offer等经典面试题中常见的题目之一。该题目不仅考察了算法基础,还考验了代码实现能力。本文将为你揭开合并两个有序链表的奥秘,手把手带你写出简洁、高效的代码。

剖析算法:直击核心思想

合并两个有序链表的算法主要有两种,分别是递归算法和迭代算法。

1. 递归算法

def mergeTwoLists(l1, l2):
    if not l1 or not l2:
        return l1 or l2
    if l1.val < l2.val:
        l1.next = mergeTwoLists(l1.next, l2)
        return l1
    else:
        l2.next = mergeTwoLists(l1, l2.next)
        return l2

2. 迭代算法

def mergeTwoLists(l1, l2):
    dummy = ListNode(0)
    curr = dummy
    while l1 and l2:
        if l1.val < l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        curr = curr.next
    curr.next = l1 or l2
    return dummy.next

手撕代码:从零开始构建

接下来,我们将从零开始手撕代码,一步步实现合并两个有序链表的功能。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def mergeTwoLists(l1, l2):
    # 创建一个虚拟头结点,用于简化代码
    dummy = ListNode(0)
    # 当前指针指向虚拟头结点
    curr = dummy
    # 循环遍历两个链表,直到其中一个链表为空
    while l1 and l2:
        # 比较两个链表当前节点的值,将较小的节点添加到合并后的链表中
        if l1.val < l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        # 当前指针指向合并后的链表的下一个节点
        curr = curr.next
    # 将剩余的节点添加到合并后的链表中
    curr.next = l1 or l2
    # 返回合并后的链表的头结点
    return dummy.next

# 测试用例
l1 = ListNode(1)
l1.next = ListNode(2)
l1.next.next = ListNode(4)

l2 = ListNode(1)
l2.next = ListNode(3)
l2.next.next = ListNode(4)

# 合并两个链表
merged_list = mergeTwoLists(l1, l2)

# 打印合并后的链表
while merged_list:
    print(merged_list.val)
    merged_list = merged_list.next

性能优化:追求极致效率

在实际应用中,链表的长度可能非常大,因此需要考虑算法的性能优化。

1. 空间优化

def mergeTwoLists(l1, l2):
    if not l1 or not l2:
        return l1 or l2
    if l1.val < l2.val:
        l1.next = mergeTwoLists(l1.next, l2)
        return l1
    else:
        l2.next = mergeTwoLists(l1, l2.next)
        return l2

在这个优化版本中,我们不再使用额外的空间来创建虚拟头结点,而是直接返回两个链表中较小的节点作为合并后的链表的头结点。这种优化可以减少空间复杂度,但会使代码的可读性略微降低。

2. 哨兵节点优化

def mergeTwoLists(l1, l2):
    # 创建一个哨兵节点,用于简化代码
    dummy = ListNode(0)
    # 当前指针指向哨兵节点
    curr = dummy
    # 循环遍历两个链表,直到其中一个链表为空
    while l1 and l2:
        # 比较两个链表当前节点的值,将较小的节点添加到合并后的链表中
        if l1.val < l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        # 当前指针指向合并后的链表的下一个节点
        curr = curr.next
    # 将剩余的节点添加到合并后的链表中
    curr.next = l1 or l2
    # 返回合并后的链表的头结点的下一个节点,因为哨兵节点不包含数据
    return dummy.next.next

这个优化版本在哨兵节点的基础上进行了进一步优化,直接返回哨兵节点的下一个节点作为合并后的链表的头结点,从而避免了额外的空间开销。这种优化可以同时提高空间复杂度和时间复杂度。

结语:从算法精粹到面试制胜

掌握合并两个有序链表的算法,不仅可以帮助你解决实际编程问题,还能为你的面试锦上添花。无论是递归算法还是迭代算法,理解其本质思想和实现细节,方能应对面试官的各种刁钻提问。希望本文能为你提供清晰的思路和有益的启发,助力你在面试中脱颖而出,迈向职业生涯的新高度。