【算法题】链表

链表是算法中最基础且高频的数据结构之一,核心难点在于指针操作的细节处理(如空指针判断、指针指向更新)。常见考点包括链表的遍历、反转、合并、重排等,需要熟练掌握指针操作的逻辑,避免空指针错误和内存泄漏。本文通过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),仅用常数级额外变量。
相关推荐
小杨同学492 小时前
【嵌入式 C 语言实战】单链表的完整实现与核心操作详解
后端·算法·架构
源代码•宸2 小时前
Golang原理剖析(map)
经验分享·后端·算法·golang·哈希算法·散列表·map
wen__xvn2 小时前
代码随想录算法训练营DAY15第六章 二叉树part03
数据结构·算法·leetcode
Sagittarius_A*2 小时前
图像滤波:手撕五大经典滤波(均值 / 高斯 / 中值 / 双边 / 导向)【计算机视觉】
图像处理·python·opencv·算法·计算机视觉·均值算法
seeksky2 小时前
Transformer 注意力机制与序列建模基础
算法
冰暮流星2 小时前
c语言如何实现字符串复制替换
c语言·c++·算法
Swift社区2 小时前
LeetCode 374 猜数字大小 - Swift 题解
算法·leetcode·swift
Coovally AI模型快速验证2 小时前
2026 CES 如何用“视觉”改变生活?机器的“视觉大脑”被点亮
人工智能·深度学习·算法·yolo·生活·无人机
有一个好名字2 小时前
力扣-链表最大孪生和
算法·leetcode·链表