考研408算法设计题保命策略:链表专题暴力解法精讲(2015 & 2019真题实战)

算法设计题在408里往往是拉开差距的"重头戏",但对时间有限的同学来说,追求满分性价比并不高 。这篇笔记针对两道链表经典真题(2015年、2019年统考真题),只讲暴力解法------保证代码逻辑正确、能跑出对的结果,把"效率优化"这部分分数主动让出去,用最低的学习成本拿到 70%~80% 的分数。


写在前面:暴力解法不是"划水",是有底线的

暴力解法能保住分数的前提,是这两点必须做到:

  1. 逻辑必须完全正确,不能因为图省事漏掉边界条件,否则不是"效率低"而是"做错了",扣分性质完全不同;
  2. 复杂度分析要如实写,暴力解是 O(n²) 就老实写 O(n²),不要照抄标准答案的 O(n) 蒙混过关------阅卷是对着代码看复杂度的,写的和代码对不上,反而显得你没理解自己写的算法。

带着这两条底线,我们来看题。


一、2015年统考真题:删除绝对值相等的重复结点

题目重述

用单链表保存 m 个整数,结点结构为 [data][link],且 |data| ≤ n(n 为正整数)。设计一个算法,对于链表中 data 的绝对值相等 的结点,仅保留第一次出现的结点,删除其余绝对值相等的结点。

举例 :链表为 21 → -15 → -15 → -7 → 15,因为第三个 -15 和最后一个 15 的绝对值都和前面出现过的重复,所以删除后变成 21 → -15 → -7

暴力解思路

这题本质就是"删除无序链表中的重复元素"的变种,最直接的想法是:对每一个结点 p,把它后面所有的结点都过一遍,凡是绝对值和 p 相同的,就删掉。

也就是经典的双重循环:外层固定一个结点,内层负责在它后面"扫雷式"删除重复项。

代码实现

c

复制代码
typedef struct LNode {
    int data;
    struct LNode *link;
} LNode, *LinkList;

void DelDup(LinkList head) {
    LNode *p = head->link;   // p指向当前要保留的结点
    LNode *pre, *q;

    while (p != NULL) {
        pre = p;              // pre记录p的直接前驱,方便内层删除结点
        q = p->link;

        while (q != NULL) {
            if (abs(q->data) == abs(p->data)) {
                pre->link = q->link;   // 绝对值重复,删除q
                free(q);
                q = pre->link;          // q后移,继续检查
            } else {
                pre = q;
                q = q->link;
            }
        }
        p = p->link;   // p移到下一个待处理结点
    }
}

复杂度分析

最坏情况下(比如所有结点绝对值都不重复),外层要走 m 次,每次内层也要扫完剩下的结点,时间复杂度为 O(m²) 。这版代码没有额外开辟数组或哈希表,空间复杂度是 O(1)

一个容易被忽略的加分细节

这题的官方标准答案(追求 O(m) 时间)用的是一个"桶数组"的技巧:因为题目明确说了 |data| ≤ n,可以开一个大小为 n+1 的数组当"标记表",遍历一遍链表,遇到绝对值第一次出现就标记,之后再遇到相同绝对值直接删除------只需遍历一次,O(m) 时间搞定,但代价是要额外开 O(n) 的辅助数组。

这里有个很有意思的对比 :暴力解法虽然时间上是 O(m²),效率更差,但空间复杂度反而是 O(1),比标准答案更省空间。如果你考场上时间紧张写不出桶数组的思路,暴力解法依然是一个逻辑自洽、站得住脚的答案,复杂度分析时如实写清楚"时间 O(m²),空间 O(1)"即可,不丢人。


二、2019年统考真题:单链表结点重排

题目重述

设线性表 L=(a1,a2,a3,⋯ ,an−2,an−1,an)L=(a_1,a_2,a_3,\cdots,a_{n-2},a_{n-1},a_n) L=(a1​,a2​,a3​,⋯,an−2​,an−1​,an​) 采用带头结点的单链表保存,结点定义如下(题目已给出,直接用即可,不需要自己重新 typedef):

c

复制代码
typedef struct node {
    int data;
    struct node *next;
} NODE;

请设计一个空间复杂度为 O(1) 且时间上尽可能高效的算法,重新排列 L 中的各结点,得到 L′=(a1,an,a2,an−1,a3,an−2,⋯ )L'=(a_1,a_n,a_2,a_{n-1},a_3,a_{n-2},\cdots) L′=(a1​,an​,a2​,an−1​,a3​,an−2​,⋯)。

⚠️ 先提醒一个关键区别

这道题和上一题不一样的地方在于:"空间复杂度O(1)"是题目明确写出来的硬性要求,不是"能省则省"的加分项 。也就是说,如果暴力解用了额外的 O(n) 空间,这不只是"没那么高效",而是直接违反了题目条件,扣分力度会比单纯的时间复杂度不达标更重。但即便如此,先把逻辑做对、拿到"正确性"这部分的分数,仍然是一笔划算的账。

暴力解思路

正规的满分解法需要用"快慢指针找中点 + 反转后半链表 + 交替合并"这一套组合技,一步想不到就容易写崩。暴力解法可以完全绕开这些技巧

用一个数组,把链表里所有结点的指针 依次存进去,这样就把"链表操作"降级成了"数组下标操作"------想要哪个位置的结点,直接下标访问就行,不用再一步步 next 遍历。存好之后,用两个下标 i(从头开始)和 j(从尾开始),按照 a1,an,a2,an−1,⋯a_1,a_n,a_2,a_{n-1},\cdots a1​,an​,a2​,an−1​,⋯ 的顺序交替取值,重新穿针引线接成新链表。

代码实现

c

复制代码
#define MAXN 10000   // 根据题目规模自行设定或用动态数组

void Rearrange(NODE *L) {
    NODE *arr[MAXN];
    NODE *p = L->next;
    int n = 0;

    // 第一步:把所有结点指针依次存入数组
    while (p != NULL) {
        arr[n++] = p;
        p = p->next;
    }

    // 第二步:双下标交替取值,重新连接
    int i = 0, j = n - 1;
    NODE *tail = L;   // tail从头结点开始,逐步向后接新结点

    while (i <= j) {
        tail->next = arr[i++];   // 先接前面的a_i
        tail = tail->next;
        if (i <= j) {             // 注意:结点数为奇数时,最后一次只需接前面,不接后面
            tail->next = arr[j--];  // 再接后面的a_j
            tail = tail->next;
        }
    }
    tail->next = NULL;   // 别忘了封尾,否则可能连成环或指向脏数据
}

复杂度分析

遍历链表存入数组是 O(n),双下标合并也是 O(n),所以时间复杂度是 O(n) ,时间效率其实已经和标准答案一样快了;但因为用了大小为 n 的数组来存指针,空间复杂度是 O(n),没有达到题目要求的 O(1)

这题暴力解丢分的地方要认清楚

维度 暴力解表现
正确性 ✅ 完全正确,能得出正确的 L'
时间复杂度 ✅ O(n),已经是最优
空间复杂度 ❌ O(n),违反了题目明确写出的"O(1)"硬性要求

这里想强调一个做题时容易忽略的阅读细节:"时间上尽可能高效"这种表述,是鼓励你往更优方向想,但没达到也不算违反题意;而像这题"空间复杂度为O(1)"这种写法,是一条明确的硬性约束,题目怎么要求就要怎么满足,这是审题时要认真区分的两种不同措辞。

如果你时间充裕,这题的满分解法(快慢指针找中点 + 反转后半段 + 合并)是一个非常值得掌握的组合技巧,因为"找中点""反转链表""合并链表"这三个操作单独拆开看,几乎是链表算法题里出现频率最高的三大基本功,值得单独用一篇笔记来精讲。


三、两题对比小结

2015年真题(删除重复) 2019年真题(结点重排)
暴力解时间复杂度 O(m²) O(n)
暴力解空间复杂度 O(1) O(n)
题目对复杂度的要求 只说"尽可能高效"(软性期望) 明确写出"空间复杂度O(1)"(硬性要求)
暴力解丢分风险 较低,只丢"效率"这一小块 较高,直接违反了明文空间要求

审题时先看清楚题目对复杂度到底是"建议"还是"要求",再决定这道题值不值得为了保底而牺牲空间去写数组辅助的暴力解------这比单纯"背一个暴力模板"更重要。

相关推荐
fangpin1 小时前
GPU编程2: 并行策略
算法
碧海银沙音频科技研究院1 小时前
8基于BES2700IHC数字助听器系统开发
嵌入式硬件·算法
HZZD_HZZD2 小时前
智能电表选CoAP还是MQTT?合众致达万表实测:CoAP省流17.3%、续航提升38%,附报文拆解与选型矩阵
算法
linux-hzh2 小时前
百日算法修炼 · Day 05
java·算法
paopaokaka_luck2 小时前
基于springboot3+vue3的云南本土影视文旅推荐平台(协同过滤算法、Echarts图形化分析)
前端·spring boot·学习·算法·echarts·mybatis
Ivanqhz3 小时前
泰勒展开(Taylor Expansion)
算法·决策树·机器学习·集成学习
zander2584 小时前
34. 在排序数组中查找元素的第一个和最后一个位置:用两个边界定位区间
数据结构·算法·leetcode
郑州光合科技余经理9 小时前
代驾系统架构拆解:订单链路、权限组织与私有化源码交付
开发语言·后端·算法·架构·系统架构·uni-app·php
To_OC11 小时前
LC 35 搜索插入位置:二分查找谁都会,边界条件谁写谁懵
javascript·算法·leetcode