【算法题】链表

链表是算法中最基础且高频的数据结构之一,核心难点在于指针操作的细节处理(如空指针判断、指针指向更新)。常见考点包括链表的遍历、反转、合并、重排等,需要熟练掌握指针操作的逻辑,避免空指针错误和内存泄漏。本文通过5道经典链表题目,覆盖链表的核心操作与解题技巧。

一、两数相加

题目描述:

两个非负整数以逆序链表形式存储(每个节点存一位数字),返回它们相加后的逆序链表。

示例

  • 输入:l1 = [2,4,3], l2 = [5,6,4],输出:[7,0,8](对应数字 342 + 465 = 807

解题思路:

模拟手工加法过程,用哑节点简化头节点处理:

  1. t 存储进位,遍历两个链表,累加当前节点值与进位。
  2. 每个位置的结果为 t % 10,新的进位为 t / 10
  3. 遍历结束后,若仍有进位则追加新节点。

完整代码:

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* cur1 = l1, * cur2 = l2;
        ListNode* newhead = new ListNode(0);
        ListNode* tail = newhead;
        int t = 0;

        while(cur1 || cur2 || t)
        {
            if(cur1)
            {
                t += cur1->val;
                cur1 = cur1->next;
            }
            if(cur2)
            {
                t += cur2->val;
                cur2 = cur2->next;
            }

            tail->next = new ListNode(t % 10);
            tail = tail->next;
            t /= 10;
        }

        tail = newhead->next;
        delete newhead;
        return tail;
    }
};

复杂度分析:

  • 时间复杂度:O(max⁡(n,m))O(\max(n,m))O(max(n,m)),nm 是两个链表的长度,遍历次数为较长链表的长度。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量(结果链表的空间为必要输出,不计入额外复杂度)。

二、两两交换链表中的节点

题目描述:

两两交换链表中的相邻节点(不能修改节点值,只能交换节点),返回交换后的头节点。

示例

  • 输入:head = [1,2,3,4],输出:[2,1,4,3]

解题思路:

用哑节点作为前驱,每次交换一对节点:

  1. 定义前驱节点 prev(初始为哑节点)、当前节点 cur、下一个节点 next、下下个节点 nnext
  2. 交换 curnextprev->next = nextnext->next = curcur->next = nnext
  3. 更新前驱指针为 cur,继续处理下一对节点。

完整代码:

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head || !head->next) return head;

        ListNode* newhead = new ListNode(0);
        newhead->next = head;
        ListNode* prev = newhead, *cur = head, *next = cur->next, *nnext = next->next;

        while(cur && next)
        {
            prev->next = next;
            next->next = cur;
            cur->next = nnext;

            prev = cur;
            cur = nnext;
            if(cur) next = cur->next;
            if(next) nnext = next->next;
        }

        cur = newhead->next;
        delete newhead;
        return cur;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),遍历链表一次。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

三、重排链表

题目描述:

将链表 L0→L1→...→Ln-1→Ln 重排为 L0→Ln→L1→Ln-1→...(不能修改节点值,只能交换节点)。

示例

  • 输入:head = [1,2,3,4],输出:[1,4,2,3]

解题思路:

三步实现:找中点→反转后半段→合并前后两段

  1. 找中点:快慢指针遍历,慢指针最终指向链表中点。
  2. 反转后半段:从中点的下一个节点开始,反转后半段链表。
  3. 合并:交替合并前半段和反转后的后半段链表。

完整代码:

cpp 复制代码
class Solution {
public:
    void reorderList(ListNode* head) {
        if(!head || !head->next || !head->next->next) return;

        // 1. 快慢指针找中点
        ListNode* slow = head, *fast = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        // 2. 反转后半段链表
        ListNode* head2 = new ListNode(0);
        ListNode* cur = slow->next;
        slow->next = nullptr; // 断链
        while(cur)
        {
            ListNode* next = cur->next;
            cur->next = head2->next;
            head2->next = cur;
            cur = next;
        }

        // 3. 合并前后两段
        ListNode* ret = new ListNode(0);
        ListNode* tail = ret;
        ListNode* cur1 = head, *cur2 = head2->next;
        while(cur1)
        {
            tail->next = cur1;
            cur1 = cur1->next;
            tail = tail->next;

            if(cur2)
            {
                tail->next = cur2;
                cur2 = cur2->next;
                tail = tail->next;
            }
        }
        delete head2;
        delete ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),找中点、反转、合并均为线性时间。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

四、合并K个升序链表

题目描述:

合并 k 个升序链表为一个升序链表。

示例

  • 输入:lists = [[1,4,5],[1,3,4],[2,6]],输出:[1,1,2,3,4,4,5,6]

解题思路:

用**最小堆(优先队列)**维护当前所有链表的头节点,每次取出最小的节点:

  1. 定义堆的比较规则(按节点值升序),将所有非空链表的头节点加入堆。
  2. 取出堆顶节点(最小节点),加入结果链表,若该节点有下一个节点则将其加入堆。
  3. 重复步骤2直到堆为空。

完整代码:

cpp 复制代码
class Solution {
    struct cmp
    {
        bool operator()(const ListNode* l1, const ListNode* l2)
        {
            return l1->val > l2->val;
        }
    };
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
        for(auto x : lists)
            if(x) heap.push(x);
        
        ListNode* ret = new ListNode(0);
        ListNode* cur = ret;
        while(!heap.empty())
        {
            ListNode* t = heap.top();
            heap.pop();
            cur->next = t;
            cur = t;
            if(t->next) heap.push(t->next);
        }

        cur = ret->next;
        delete ret;
        return cur;
    }
};

复杂度分析:

  • 时间复杂度:O(Nlog⁡k)O(N \log k)O(Nlogk),N 是所有链表的总节点数,每个节点入堆/出堆的时间为 O(log⁡k)O(\log k)O(logk)。
  • 空间复杂度:O(k)O(k)O(k),堆的大小最多为 k(存储所有链表的头节点)。

五、K个一组翻转链表

题目描述:

k 个节点一组翻转链表,最后不足 k 个的节点保持原有顺序。

示例

  • 输入:head = [1,2,3,4,5], k = 2,输出:[2,1,4,3,5]

解题思路:

先统计节点数确定翻转组数,再逐组翻转:

  1. 统计链表总节点数,计算需要翻转的组数 n = 总节点数 / k
  2. 用哑节点作为前驱,每组翻转 k 个节点,更新前驱指针为该组的最后一个节点。
  3. 处理完所有组后,将剩余不足 k 个的节点直接连接到结果链表。

完整代码:

cpp 复制代码
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* cur = head;
        int n = 0;
        while(cur)
        {
            n++;
            cur = cur->next;
        }
        n /= k; // 翻转的组数

        ListNode* newhead = new ListNode(0);
        ListNode* prev = newhead;
        cur = head;
        while(n--)
        {
            ListNode* tmp = cur;
            // 翻转当前组的k个节点
            for(int i = 0; i < k; i++)
            {
                ListNode* next = cur->next;
                cur->next = prev->next;
                prev->next = cur;
                cur = next;
            }
            prev = tmp; // 更新前驱为当前组的最后一个节点
        }
        prev->next = cur; // 连接剩余节点

        cur = newhead->next;
        delete newhead;
        return cur;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),遍历链表统计节点数,每组翻转均为线性时间。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
相关推荐
网安INF12 分钟前
数据结构第三章:栈、队列和数组
数据结构
hetao173383733 分钟前
2026-04-09~12 hetao1733837 的刷题记录
c++·算法
6Hzlia34 分钟前
【Hot 100 刷题计划】 LeetCode 136. 只出现一次的数字 | C++ 哈希表&异或基础解法
c++·算法·leetcode
MWWZ1 小时前
最近的一些软件更新
opencv·算法·计算机视觉
CoovallyAIHub1 小时前
视频理解新范式:Agent不再被动看视频,LensWalk让它自己决定看哪里
算法·架构·github
CoovallyAIHub1 小时前
斯坦福丨AirVLA:将地面机械臂模型迁移至无人机实现空中抓取,成功率从23%提升至50%
算法·架构·github
yuannl102 小时前
数据结构----双端队列实现
数据结构
无限进步_2 小时前
【C++】只出现一次的数字 II:位运算的三种解法深度解析
数据结构·c++·ide·windows·git·算法·leetcode
Takoony2 小时前
GPU 推理并发的本质:从第一性原理到工程实践
算法·gru
qq_454245032 小时前
通用引用管理框架
数据结构·架构·c#