力扣刷题-86.分隔链表

一、问题现象:AddressSanitizer 报 SEGV 错误

1. 错误日志

复制代码
Line 17: Char 22:
AddressSanitizer:DEADLYSIGNAL
==22==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x560396d93c39 bp 0x7ffef0182410 sp 0x7ffef01823d0 T0)
==22==The signal is caused by a READ memory access.
==22==Hint: address points to the zero page.
    #0 0x560396d93c39 in Solution::partition(ListNode*, int) solution.cpp:17:22
SUMMARY: AddressSanitizer: SEGV solution.cpp:17:22 in Solution::partition(ListNode*, int)

2. 错误解读

  • SEGV on unknown address 0x000000000000:核心问题是空指针解引用(尝试读取地址为 0 的内存,这是系统保护的无效内存);
  • 错误定位:solution.cpp 第 17 行的 partition 函数,是链表遍历过程中触发的内存访问错误。

二、初始错误代码分析

我的初始思路是用 vector 收集 "小于 x" 和 "大于等于 x" 的节点,再拼接成新链表,代码如下:

cpp 复制代码
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        if(!head||!head->next) return head;
        vector<ListNode*> vec;
        // 第一遍遍历:收集小于x的节点(逻辑正常)
        for(ListNode *l=head;l;l=l->next){
            if(l->val<x)
            vec.push_back(l);
        }
        // 第二遍遍历:收集大于等于x的节点(致命错误)
        for(ListNode* l;l;l=l->next){ // 未初始化l!
            if(l->val>=x)
            vec.push_back(l);
        }
        // 拼接链表(次要问题:尾节点未置空)
        for(int i=0;i<vec.size()-1;++i){
            vec[i]->next=vec[i+1];
        }
        return vec[0];
    }
};

3. 核心问题拆解

(1)致命错误:循环变量未初始化(直接触发 SEGV)

第二个 for 循环中 ListNode* l; 仅声明了指针,但未初始化,此时 l 的值是随机的垃圾值(大概率是 0x0,即空指针)。

  • 循环条件 l; 会直接判断这个垃圾值,若为 0 则循环不执行,若为非 0 则访问 l->next,触发空指针解引用;
  • 这是导致 AddressSanitizer 报 SEGV 的直接原因(第 17 行的核心问题)。
(2)次要但关键的问题:链表尾节点未置空

拼接链表后,最后一个节点的 next 仍指向原链表的节点,会导致:

  • 链表出现环,遍历时报错;
  • 访问原链表的无效内存,触发潜在的内存错误。

三、修复思路与正确代码

1. 修复核心原则

  • 所有指针使用前必须初始化;
  • 访问 node->val/node->next 前,先检查 node 是否为空;
  • 链表拼接完成后,尾节点的 next 必须置空。

2. 修复后的完整代码

cpp 复制代码
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        // 边界条件:空链表或单节点链表直接返回
        if(!head || !head->next) return head;
        
        vector<ListNode*> vec;
        // 第一遍遍历:收集小于x的节点(初始化l=head)
        for(ListNode *l = head; l; l = l->next) {
            if(l->val < x) {
                vec.push_back(l);
            }
        }
        // 第二遍遍历:收集大于等于x的节点(关键修复:初始化l=head)
        for(ListNode *l = head; l; l = l->next) {
            if(l->val >= x) {
                vec.push_back(l);
            }
        }
        
        // 拼接链表
        for(int i = 0; i < vec.size() - 1; ++i) {
            vec[i]->next = vec[i+1];
        }
        // 关键修复:尾节点next置空,避免环或无效访问
        vec.back()->next = nullptr;
        
        return vec[0];
    }
};

3. 关键修改点

表格

问题点 修复方案
循环变量 l 未初始化 第二个循环中 l 初始化为 head,确保从链表头开始遍历
尾节点 next 未置空 添加 vec.back()->next = nullptr;,终止链表

四、进阶优化:O (1) 空间复杂度方案

上述方案用 vector 存储节点,空间复杂度为 O (n),链表题的最优解通常要求 O (1) 空间,可通过虚拟头节点实现:

cpp 复制代码
ListNode* partition(ListNode* head, int x) {
    // 定义两个虚拟头节点,分别存储小于x和大于等于x的节点
    ListNode* dummy1 = new ListNode(0);
    ListNode* dummy2 = new ListNode(0);
    ListNode* p1 = dummy1; // 遍历dummy1的指针
    ListNode* p2 = dummy2; // 遍历dummy2的指针
    
    // 一次遍历完成分区,避免重复遍历
    while(head) {
        if(head->val < x) {
            p1->next = head;
            p1 = p1->next;
        } else {
            p2->next = head;
            p2 = p2->next;
        }
        head = head->next;
    }
    
    // 尾节点置空 + 拼接两个链表
    p2->next = nullptr;
    p1->next = dummy2->next;
    
    // 释放虚拟节点,避免内存泄漏
    ListNode* res = dummy1->next;
    delete dummy1;
    delete dummy2;
    
    return res;
}

优化点:

  1. 仅遍历链表一次,时间复杂度保持 O (n);
  2. 无需额外容器存储节点,空间复杂度降为 O (1);
  3. 虚拟头节点避免了空指针判断的冗余代码,逻辑更简洁。
相关推荐
智算菩萨2 小时前
上下文学习的贝叶斯推断视角:隐式梯度下降还是隐式贝叶斯?
人工智能·算法
TracyCoder1232 小时前
LeetCode Hot100(52/100)——394. 字符串解码
算法·leetcode·职场和发展
thginWalker2 小时前
leetcode有空可以挑战的题目
leetcode
52Hz1182 小时前
力扣207.课程表、208.实现Trie(前缀树)
python·leetcode
Σίσυφος19002 小时前
四元数 欧拉角 旋转矩阵
人工智能·算法·矩阵
shentuyu木木木(森)2 小时前
单调队列 & 单调栈
数据结构·c++·算法·单调栈·单调队列
ghie90902 小时前
基于MATLAB的指纹定位算法仿真实现
数据库·算法·matlab
熬了夜的程序员2 小时前
【LeetCode】119. 杨辉三角 II
算法·leetcode·职场和发展
sali-tec3 小时前
C# 基于OpenCv的视觉工作流-章24-SURF特征点
图像处理·人工智能·opencv·算法·计算机视觉