本文是LeetCode算法入门刷题笔记,好记性不如烂笔头,多背然后自己多默写几遍自然就理解了。若有错误的地方也请各位大佬指正哈
首先让我们回顾一下链表定义:
链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接,有的叫指针。
链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。
- 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。
- 尾节点指向的是“空”,它在Java、C++ 和Python 中分别被记为null、nullptr 和None 。
- 在C、C++、Go 和Rust 等支持指针的语言中,上述“引用”应被替换为“指针”。
-
1. 反转链表
-
方法一:迭代(双指针)
思路:遍历链表,并在访问各节点时修改next指针指向
-
class Solution: def reverse_list(self, head): cur, pre = head, None while cur: temp = cur.next cur.next = pre pre = cur cur = temp return pre
时间复杂度:O(n)
空间复杂度:O(1),变量pre和cur使用常数大小额外空间
方法二:递归
思路:当越过尾节点后终止递归,在回溯时修改各节点的next指针指向
-
class Solution: def reverseList(self, head): def recur(cur, pre): if not cur: return pre res = recur(cur.next, cur) cur.next = pre return res return recur(head, None)
时间复杂度:O(n)
空间复杂度:O(n),遍历链表的递归深度达到n,系统使用O(n)大小额外空间。
小结:考虑什么时候是循环终止条件,当然是链表为空的时候,所以方法一(迭代)循环开始是while cur:,方法二(递归),当越过尾节点后终止递归,if not cur: return pre。
为了方便查看,打印链表。
-
class ListNode: def __init__(self, val): self.val = val self.next = None def printlist(head): cur = head while cur: print(cur.val, end='->' if cur.next else " ") cur = cur.next print() #双指针,迭代 class Solution: def reverselist(self, head): cur, pre = head, None while cur: temp = cur.next cur.next = pre pre = cur cur = temp return pre #递归 class Solution2: def reverselist(self, head): def recur(cur, pre): if not cur: return pre res = recur(cur.next, cur) cur.next = pre return res return recur(head, None) n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5) n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 print("原始链表:") printlist(n1) result = Solution2() out_reverselist = result.reverselist(n1) print("反转链表:") printlist(out_reverselist)
2. 奇偶链表
这道题我刚开始做的时候,老是会丢掉一个循环终止条件,画图加深印象!!!
思路:将奇数节点和偶数节点分开,然后将奇数尾节点指向偶数头节点,可以分成两种情况,链表长度为奇数和偶数(循环终止条件不同)。
情况1:链表长度为奇数
-
-
循环终止条件:even为none
情况2:链表长度为偶数
循环终止条件:even.next为none
-
-
class Solution: def oddevenlist(self, head): if not head: return head evenhead = head.next odd, even = head, evenhead while even and even.next: odd.next = even.next odd = odd.next even.next = odd.next even = even.next odd.next = evenhead return head
时间复杂度:O(n),其中n是链表的节点数。需要遍历链表中的每个节点,并更新指针。
空间复杂度:O(1),只需要维护有限的指针
为方便查看,打印链表
-
class ListNode: def __init__(self, val): self.val = val self.next = None def printlist(head): cur = head while cur: print(cur.val, end='->' if cur.next else " ") cur = cur.next print() #换行 class Solution: def oddevenlist(self, head): if not head: return head evenhead = head.next odd, even = head, evenhead while even and even.next: odd.next = even.next odd = odd.next even.next = odd.next even = even.next odd.next = evenhead return head n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5) n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 print("原始链表:") printlist(n1) result = Solution() out_reverselist = result.oddevenlist(n1) print("转换后链表:") printlist(out_reverselist)
3. 合并两个有序链表
思路:由于两个链表是有序的,用双指针list1,list2遍历两个链表,比较list1.val跟list2.val大小确定添加哪个节点,想想终止条件,只要某个链表节点为none,跳出循环,记住不要忘记cur.next = list1 if list1 else list2,确保不落下最后一个节点。图引用的是Krahets大佬的。
class Solution:
def mergelist(self, list1, list2):
cur = dummy = ListNode(0)
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return dummy.next
时间复杂度O(m+n):m、n分别为list1、list2的长度,合并操作需遍历两链表。
空间复杂度O(1):节点引用dummy,cur使用常数大小的额外空间。
为方便查看,打印链表
class Solution:
def mergelist(self, list1, list2):
cur = dummy = ListNode(0)
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return dummy.next
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
def printlist(head):
cur = head
while cur:
print(cur.val, end='->' if cur.next else " ")
cur = cur.next
print() #换行
n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
print("原始链表1:")
printlist(n1)
p1, p2, p3 = ListNode(2), ListNode(4), ListNode(7)
p1.next, p2.next = p2, p3
print("原始链表2:")
printlist(p1)
result = Solution()
out_mergelist = result.mergelist(n1, p1)
print("合并后链表:")
printlist(out_mergelist)
4. 合并K个有序链表
思路:用最小堆实现。初始把所有链表的头节点入堆,然后不断弹出堆中最小节点x,如果x.next不为空就加入堆中。循环直到堆为空。把弹出的节点按顺序拼接起来,就得到了答案。参考灵神大佬
ListNode.__lt__ = lambda a, b: a.val < b.val
class Solution:
def mergeKLists(self, lists):
cur = dummy = ListNode()
h = [head for head in lists if head]
heapify(h)
while h:
code = heappop(h)
if code.next:
heappush(h, code.next)
cur.next = code
cur = cur.next
return dummy.next
Note:在Pycharm中要加上这一句from heapq import heappop, heapify, heappush
时间复杂度:O(nlogk),其中k为lists的长度,n为所有链表的节点数之和。
空间复杂度:O(k)。堆中至多有k个元素(每个链表的头节点)
为方便查看,打印链表
from heapq import heappush, heappop, heapify
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
ListNode.__lt__ = lambda a, b: a.val < b.val
class Solution:
def mergeKlist(self, lists):
cur = dummy = ListNode(0)
h = [head for head in lists if head]
heapify(h)
while h:
node = heappop(h)
if node.next:
heappush(h, node.next)
cur.next = node
cur = cur.next
return dummy.next
def printlist(head):
cur = head
while cur:
print(cur.val, end='->' if cur.next else " ")
cur = cur.next
print() #换行
n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
print("原始链表1:")
printlist(n1)
p1, p2, p3 = ListNode(2), ListNode(4), ListNode(7)
p1.next, p2.next = p2, p3
print("原始链表2:")
printlist(p1)
q1, q2, q3 = ListNode(3), ListNode(8), ListNode(9)
q1.next, q2.next = q2, q3
print("原始链表3:")
printlist(q1)
result = Solution()
out_mergelist = result.mergeKlist([n1, p1, q1])
print("合并后链表:")
printlist(out_mergelist)
5. 排序链表
思路:采用归并排序解题,将链表从中间分成两部分,递归地对左半部分和右半部分进行排序,然后合并两个有序链表。
链表拆分:使用快慢指针找到链表的中间节点,将链表从中间断开,分为左半部分和右半部分。
链表合并:合并两个有序链表,返回合并后的链表头节点
class Solution:
def mergelist(self, head):
# 终止条件:链表为空或只有一个节点
if not head or not head.next:
return head
# 分割链表
slow = fast = head
pre = None
#考虑链表长度为奇数或偶数情况,与第2题奇偶链表类似
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
pre.next = None #断开链表
mid = slow #右半部分头节点
#递归排序左半部分和右半部分
left = self.mergelist(head)
right = self.mergelist(mid)
return self.merge(left, right)
def merge(self, list1, list2):
cur = dummy = ListNode(0)
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return dummy.next
时间复杂度:O(nlogn),其中n是链表长度。递归式T(n)=2T(n/2)+O(n),由主定理可得时间复杂度为O(nlogn)。证明链接在这里。
空间复杂度:O(logn)。递归需要O(logn)的栈开销
为方便查看,打印链表
class Solution:
def mergelist(self, head):
if not head or not head.next:
return head
slow = fast = head
pre = None
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
pre.next = None
mid = slow
left = self.mergelist(head)
right = self.mergelist(mid)
return self.merge(left, right)
def merge(self, list1, list2):
cur = dummy = ListNode(0)
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return dummy.next
class ListNode:
def __init__(self, val):
self.val = val
self.next = None
def printlist(head):
cur = head
while cur:
print(cur.val, end='->' if cur.next else " ")
cur = cur.next
print() #换行
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)
result = Solution()
out_mergelist = result.mergelist(n1)
print("排序后链表:")
printlist(out_mergelist)
6. 删除链表的节点
思路:遍历链表,找到所有值为val的节点并删除,图是Krahets大佬的。
class Solution:
def deletelist(self, head, val):
if not head: return head #确保链表非空
#LeetCode官网直接用cur = dummy = ListNode()就可以
#这里Pycharm会报错,要定义下
cur = dummy = ListNode(0, next=head)
if head.val == val: return head.next
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
时间复杂度O(n):n为链表长度,删除操作平均需循环n/2次,最差n次。
空间复杂度O(1):cur, dummy占用常数大小额外空间
为方便查看,打印链表
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
class Solution:
def deletelist(self, head, val):
if not head: return head
cur = dummy = ListNode(0, next=head)
if head.val == val: return head.next
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
def printlist(head):
cur = head
while cur:
print(cur.val, end='->' if cur.next else " ")
cur = cur.next
print() #换行
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)
result = Solution()
out_mergelist = result.deletelist(n1, 3)
print("删除后链表:")
printlist(out_mergelist)
7. 删除链表的倒数第N个节点
思路:采用双指针遍历链表,右指针比左指针提前走n步,当右指针走到头,左指针正好是倒数第N个节点的前一个节点,这里一定要自己动手画一下两个指针如何前进的。
class Solution:
def deleteKlist(self, head, n):
if not head: return head #确保链表非空
left = right = dummy = ListNode(0, next=head)
#右指针先走n步
for _ in range(n):
right = right.next
while right.next:
left = left.next
right = right.next
left.next = left.next.next #左指针的下一个节点就是倒数第N个节点
return dummy.next
时间复杂度:O(n, n 为链表的长度。
空间复杂度:O(1),仅用到若干额外变量
为方便查看,打印链表
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
def printlist(head):
cur = head
while cur:
print(cur.val, end='->' if cur.next else " ")
cur = cur.next
print() #换行
class Solution:
def deleteKlist(self, head, n):
if not head: return head
left = right = dummy = ListNode(0, next=head)
for _ in range(n):
right = right.next
while right.next:
left = left.next
right = right.next
left.next = left.next.next
return dummy.next
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)
result = Solution()
out_mergelist = result.deleteKlist(n1, 2)
print("删除后链表:")
printlist(out_mergelist)