引言 参照的是左程云的课程:https://space.bilibili.com/8888480/lists/3509640?type=series 本笔记包括了class009 -> class012,涵盖了链表的基础概念、反转操作、合并算法、链表运算以及分割技巧等内容。
009【入门】单双链表及其反转 链表的基本概念 链表是一种线性数据结构,其中元素存储在节点中,每个节点包含数据和指向下一个节点的指针。与数组不同,链表的元素在内存中不必连续存储。
单链表节点定义 1 2 3 4 5 6 7 class ListNode : """ 单链表节点类 """ def __init__ (self, val=0 , next =None ): self .val = val self .next = next
双链表节点定义 1 2 3 4 5 6 7 8 9 class DoubleListNode : """ 双链表节点类 每个节点有两个指针:指向前驱和后继 """ def __init__ (self, value ): self .value = value self .last = None self .next = None
单链表反转算法 反转单链表测试链接 : https://leetcode.cn/problems/reverse-linked-list/
迭代方法实现 单链表反转是链表操作中的经典问题,核心思想是改变节点间的指针方向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class ListReverseOperations : @staticmethod def reverse_linked_list (head ): """ 反转单链表 - 迭代实现 时间复杂度: O(n), 空间复杂度: O(1) 参数: head - 链表头节点 返回: 反转后链表的头节点 """ pre = None next_node = None while head is not None : next_node = head.next head.next = pre pre = head head = next_node return pre
反转过程图解
具体在上面的实现中,是利用next向后移动 利用pre改变方指针向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 原链表: 1 -> 2 -> 3 -> 4 -> 5 -> NULL 第1步: pre=NULL, head=1, next=2 NULL <- 1 2 -> 3 -> 4 -> 5 -> NULL pre head 第2步: pre=1, head=2, next=3 NULL <- 1 <- 2 3 -> 4 -> 5 -> NULL pre head 第3步: pre=2, head=3, next=4 NULL <- 1 <- 2 <- 3 4 -> 5 -> NULL pre head 最终: NULL <- 1 <- 2 <- 3 <- 4 <- 5 pre
递归方法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @staticmethod def reverse_linked_list_recursive (head ): """ 反转单链表 - 递归实现 时间复杂度: O(n), 空间复杂度: O(n) - 递归栈空间 参数: head - 链表头节点 返回: 反转后链表的头节点 """ if head is None or head.next is None : return head new_head = ListReverseOperations.reverse_linked_list_recursive(head.next ) head.next .next = head head.next = None return new_head
双链表反转算法 双链表的反转需要同时处理前驱和后继两个指针,相比单链表更加复杂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @staticmethod def reverse_double_list (head ): """ 反转双链表 时间复杂度: O(n), 空间复杂度: O(1) 参数: head - 双链表头节点 返回: 反转后双链表的头节点 """ pre = None next_node = None while head is not None : next_node = head.next head.next = pre head.last = next_node pre = head head = next_node return pre
双链表反转的关键点
指针交换 :每个节点的next和last指针需要互换方向
边界处理 :正确处理链表两端的NULL指针
遍历顺序 :确保在修改指针前保存必要的信息
010【入门】合并两个有序链表 测试链接 : https://leetcode.cn/problems/merge-two-sorted-lists/
问题描述 给定两个已排序的链表,将它们合并成一个新的有序链表。新链表应该通过拼接给定的两个链表的所有节点组成。
算法思想 采用双指针技术 ,比较两个链表当前节点的值,选择较小的节点添加到结果链表中,然后移动对应的指针。
实现方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution : @staticmethod def merge_two_lists (head1, head2 ): """ 合并两个有序链表 时间复杂度: O(m + n), 空间复杂度: O(1) 参数: head1, head2 - 两个有序链表的头节点 返回: 合并后有序链表的头节点 """ if head1 is None or head2 is None : return head2 if head1 is None else head1 if head1.val <= head2.val: head = head1 cur1 = head1.next cur2 = head2 else : head = head2 cur1 = head1 cur2 = head2.next pre = head while cur1 is not None and cur2 is not None : if cur1.val <= cur2.val: pre.next = cur1 cur1 = cur1.next else : pre.next = cur2 cur2 = cur2.next pre = pre.next pre.next = cur1 if cur1 is not None else cur2 return head
算法优化版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @staticmethod def merge_two_lists_optimized (head1, head2 ): """ 合并两个有序链表 - 优化版本 使用虚拟头节点简化边界处理 """ dummy = ListNode(0 ) current = dummy while head1 is not None and head2 is not None : if head1.val <= head2.val: current.next = head1 head1 = head1.next else : current.next = head2 head2 = head2.next current = current.next current.next = head1 if head1 is not None else head2 return dummy.next
合并过程示例 1 2 3 4 5 6 7 8 9 10 链表1: 1 -> 2 -> 4 链表2: 1 -> 3 -> 4 合并过程: step1: 比较1和1,选择链表1的1 结果: 1 step2: 比较2和1,选择链表2的1 结果: 1 -> 1 step3: 比较2和3,选择链表1的2 结果: 1 -> 1 -> 2 step4: 比较4和3,选择链表2的3 结果: 1 -> 1 -> 2 -> 3 step5: 比较4和4,选择链表1的4 结果: 1 -> 1 -> 2 -> 3 -> 4 step6: 连接剩余的4 结果: 1 -> 1 -> 2 -> 3 -> 4 -> 4
011【入门】两个链表相加 测试链接:https://leetcode.cn/problems/add-two-numbers/
问题描述 给定两个非空链表来表示两个非负整数,数字最高位位于链表开始位置。它们的每个节点只存储一位数字,计算两个数的和并以相同形式返回一个表示和的链表。
核心思想 模拟手工加法运算,从链表尾部开始逐位相加,处理进位问题。
算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution : @staticmethod def add_two_numbers (h1, h2 ): """ 两个链表数字相加 时间复杂度: O(max(m,n)), 空间复杂度: O(max(m,n)) 参数: h1, h2 - 两个表示数字的链表头节点 返回: 表示和的链表头节点 """ ans = None cur = None carry = 0 while h1 is not None or h2 is not None : val1 = h1.val if h1 is not None else 0 val2 = h2.val if h2 is not None else 0 total = val1 + val2 + carry carry = total // 10 digit = total % 10 if ans is None : ans = ListNode(digit) cur = ans else : cur.next = ListNode(digit) cur = cur.next h1 = h1.next if h1 is not None else None h2 = h2.next if h2 is not None else None if carry == 1 : cur.next = ListNode(1 ) return ans
代码修正与优化 优化了源代码最后的进位问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @staticmethod def add_two_numbers_corrected (h1, h2 ): """ 两个链表数字相加 - 优化版本,更加通用 """ ans = None cur = None carry = 0 while h1 is not None or h2 is not None : val1 = h1.val if h1 is not None else 0 val2 = h2.val if h2 is not None else 0 total = val1 + val2 + carry carry = total // 10 digit = total % 10 new_node = ListNode(digit) if ans is None : ans = cur = new_node else : cur.next = new_node cur = new_node h1 = h1.next if h1 is not None else None h2 = h2.next if h2 is not None else None if carry > 0 : cur.next = ListNode(carry) return ans
算法示例 1 2 3 4 5 6 7 8 9 10 链表1: 2 -> 4 -> 3 (表示数字342) 链表2: 5 -> 6 -> 4 (表示数字465) 相加过程: 位置0: 2 + 5 + 0(进位) = 7, 进位=0 结果: 7 位置1: 4 + 6 + 0(进位) = 10, 进位=1 结果: 7 -> 0 位置2: 3 + 4 + 1(进位) = 8, 进位=0 结果: 7 -> 0 -> 8 最终结果: 7 -> 0 -> 8 (表示数字807) 验证: 342 + 465 = 807 ✓
边界情况处理
不同长度链表 :短链表结束后,继续处理长链表的剩余位
最高位进位 :最后可能产生新的最高位
空链表 :输入验证,确保链表非空
单位数 :正确处理个位数相加的情况
012【入门】划分链表 测试链接 : https://leetcode.cn/problems/partition-list/
问题描述 给定一个链表和一个特定值x,对链表进行分隔,使得所有小于x的节点都在大于或等于x的节点之前。保持两个分区中每个节点的初始相对位置。
算法思想 使用双链表分离 的思想:
创建两个独立的链表:小于x的节点链表和大于等于x的节点链表
遍历原链表,将节点分别添加到对应的链表中
最后将两个链表连接起来
实现方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution : @staticmethod def partition (head, x ): """ 划分链表 时间复杂度: O(n), 空间复杂度: O(1) 参数: head - 链表头节点, x - 划分值 返回: 划分后链表的头节点 """ left_head = None left_tail = None right_head = None right_tail = None next_node = None while head is not None : next_node = head.next head.next = None if head.val < x: if left_head is None : left_head = head else : left_tail.next = head left_tail = head else : if right_head is None : right_head = head else : right_tail.next = head right_tail = head head = next_node if left_head is None : return right_head else : left_tail.next = right_head return left_head
代码修正与完善 优化了原代码在遍历阶段的指针指代可能不清晰的问题,修改了if else后的指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution : @staticmethod def partition_corrected (head, x ): """ 划分链表 - 修正版本 修复了原代码的语法错误和逻辑问题 """ left_head = left_tail = None right_head = right_tail = None while head is not None : next_node = head.next head.next = None if head.val < x: if left_head is None : left_head = left_tail = head else : left_tail.next = head left_tail = head else : if right_head is None : right_head = right_tail = head else : right_tail.next = head right_tail = head head = next_node if left_head is None : return right_head left_tail.next = right_head return left_head
优化版本:使用虚拟头节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @staticmethod def partition_optimized (head, x ): """ 划分链表 - 优化版本 使用虚拟头节点简化代码逻辑,简化了边界处理 """ left_dummy = ListNode(0 ) right_dummy = ListNode(0 ) left = left_dummy right = right_dummy while head is not None : if head.val < x: left.next = head left = left.next else : right.next = head right = right.next head = head.next right.next = None left.next = right_dummy.next return left_dummy.next
算法示例 1 2 3 4 5 6 7 8 9 10 11 12 原链表: 1 -> 4 -> 3 -> 2 -> 5 -> 2 划分值: x = 3 分离过程: 节点1 < 3: 左链表 = 1 节点4 >= 3: 右链表 = 4 节点3 >= 3: 右链表 = 4 -> 3 节点2 < 3: 左链表 = 1 -> 2 节点5 >= 3: 右链表 = 4 -> 3 -> 5 节点2 < 3: 左链表 = 1 -> 2 -> 2 最终结果: 1 -> 2 -> 2 -> 4 -> 3 -> 5
算法特点
稳定性 :保持原有的相对顺序
原地操作 :只调整指针,不创建新节点
时间效率 :单次遍历,O(n)时间复杂度
空间效率 :只使用常数额外空间
应用场景
链表排序的预处理 :快速排序的分区操作
数据分类 :按条件将数据分为两组
链表重组 :根据特定规则重新排列链表节点
链表操作技巧拓展 核心技巧与模式 1. 双指针技术 1 2 3 4 5 6 7 8 9 10 def two_pointer_pattern (head ): """ 双指针模式:快慢指针、左右指针等 常用于链表中点查找、环检测、倒数第k个节点等 """ slow = fast = head while fast and fast.next : slow = slow.next fast = fast.next .next return slow
2. 虚拟头节点 1 2 3 4 5 6 7 8 9 10 11 12 def dummy_head_pattern (head ): """ 虚拟头节点模式:简化头节点的特殊处理 特别适用于可能删除头节点或构建新链表的场景 """ dummy = ListNode(0 ) dummy.next = head return dummy.next
3. 递归模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def recursive_pattern (head ): """ 递归模式:将复杂问题分解为子问题 适用于链表反转、删除节点、合并等操作 """ if head is None or head.next is None : return head result = recursive_pattern(head.next ) return result
4. 节点分离与重组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 def separate_and_merge_pattern (head ): """ 分离重组模式:将链表按条件分离后重新组合 适用于链表划分、奇偶分离、按值分组等 """ list1_head = list1_tail = None list2_head = list2_tail = None while head: next_node = head.next head.next = None if condition(head): if list1_head is None : list1_head = list1_tail = head else : list1_tail.next = head list1_tail = head else : if list2_head is None : list2_head = list2_tail = head else : list2_tail.next = head list2_tail = head head = next_node if list1_tail: list1_tail.next = list2_head return list1_head if list1_head else list2_head
常见错误与注意事项 1. 空指针处理 1 2 3 4 5 6 7 8 9 def wrong_example (head ): return head.next .val def correct_example (head ): if head and head.next : return head.next .val return None
2. 内存泄漏防止 1 2 3 4 5 6 7 8 def prevent_memory_leak (head ): """ 防止内存泄漏:及时断开不需要的连接 """ while head: next_node = head.next head.next = None head = next_node
3. 边界情况处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def handle_edge_cases (head ): """ 处理边界情况:空链表、单节点链表等 """ if head is None : return None if head.next is None : return head
性能分析与优化 时间复杂度分析
单次遍历操作 :O(n) - 反转、合并、查找等
嵌套遍历操作 :O(n²) - 某些复杂的链表操作
递归操作 :O(n) - 但需要考虑递归栈空间
空间复杂度优化
原地操作 :优先使用指针操作而非创建新节点
迭代替代递归 :在可能的情况下避免递归栈开销
临时变量最小化 :只保存必要的指针变量
实际性能考虑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def performance_optimized_merge (h1, h2 ): """ 性能优化的链表合并 减少条件判断和指针操作 """ dummy = ListNode(0 ) tail = dummy while h1 and h2: if h1.val <= h2.val: tail.next , h1 = h1, h1.next else : tail.next , h2 = h2, h2.next tail = tail.next tail.next = h1 or h2 return dummy.next