题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
# 合并两个有序链表
class ListNode(object):
"""定义一个链表节点类"""
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class LinkList(object):
"""定义一个链表类,该类使用上面定义的ListNode作为节点,通过在链表尾部添加节点来创建链表"""
def __init__(self):
self.head = None
def initlist(self,data):
self.head = ListNode(data[0])
p = self.head
for i in data[1:]:
node = ListNode(i)
p.next = node
p = p.next
def mergeTwoLists(self,l1,l2):
"""合并两个有序链表"""
dummy = ListNode(-1) # 虚拟头结点
p = dummy # p用于遍历的节点
p1 = l1
p2 = l2
# 当两个链表都不为空时
while p1 and p2 :
# 比较p1和p2两个指针
# 将值较小的节点接到p指针
if p1.val < p2.val:
p.next = p1
p1 = p1.next
else:
p.next = p2
p2 = p2.next
# p指针不断前进
p = p.next
#如果链表1还有剩余节点
if p1:
p.next = p1
#如果链表2还有剩余节点
if p2:
p.next = p2
return dummy.next
def printList(self):
node = self.head
while node:
print(node.val)
node = node.next
data1 = [1,2,4]
data2 = [2,3,5]
l1 = LinkList()
l2 = LinkList()
l1.initlist(data1)
l2.initlist(data2)
l1.printList()
l2.printList()
merged_list = LinkList() # 创建一个新的LinkList对对象merged_list
merged_list.head = l1.mergeTwoLists(l1.head, l2.head) # 将l1和l2的头节点传递给mergeTwoLists,并将返回的结果赋给merged_list的头节点
merged_list.printList()
# 创建并初始化链表(方法一):
data1 = [1,2,4]
data2 = [2,3,5]
l1 = LinkList()
l2 = LinkList()
l1.initlist(data1)
l2.initlist(data2)
# 创建并初始化链表(方法二):
l1 = LinkList()
l2 = LinkList()
l1.initlist([1,2,4])
l2.initlist([1,3,4])
# 合并两个链表
merged_list = LinkList()
merged_list.head = l1.mergeTwoLists(l1.head, l2.head)
# merged_list.head = l2.mergeTwoLists(l1.head, l2.head)
创建了一个新的LinkList
对象merged_list
,并将l1
和l2
的头节点传递给mergeTwoLists
方法,并将返回的结果赋值给merged_list
的头节点。
在这两行代码中,l1.mergeTwoLists(l1.head, l2.head)
和 l2.mergeTwoLists(l1.head, l2.head)
,我们都是调用 mergeTwoLists
方法来合并 l1
和 l2
两个链表。
这两行代码的唯一区别在于我们调用 mergeTwoLists
方法的对象不同。在第一行代码中,我们在 l1
对象上调用 mergeTwoLists
方法,而在第二行代码中,我们在 l2
对象上调用 mergeTwoLists
方法。
然而,这两行代码的结果是相同的,因为 mergeTwoLists
方法的功能是合并两个链表,而不是依赖于它被调用的对象。无论我们在哪个对象上调用 mergeTwoLists
方法,它都会返回合并后的链表。
所以,你可以根据你的需要选择使用哪一行代码。如果你想要在 l1
对象上调用 mergeTwoLists
方法,你应该使用第一行代码。如果你想要在 l2
对象上调用 mergeTwoLists
方法,你应该使用第二行代码。
代码可视化动画
形象地理解,这个算法的逻辑类似于拉拉链,l1, l2
类似于拉链两侧的锯齿,指针 p
就好像拉链的拉索,将两个有序链表合并;或者说这个过程像蛋白酶合成蛋白质,l1, l2
就好比两条氨基酸,而指针 p
就好像蛋白酶,将氨基酸组合成蛋白质。
代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy
节点。你可以试试,如果不使用 dummy
虚拟节点,代码会复杂一些,需要额外处理指针 p
为空的情况。而有了 dummy
节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。
么时候需要用虚拟头结点?我这里总结下:当你需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,让你把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比你想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。