单链表每个节点包含的两个部分是( )。
考点:节点两域结构(A1)。
(A1)考点:节点两域结构——链表节点由数据域与指针域组成,指针域存下一节点地址。
解析:本题考查节点两域结构。数据域装内容,指针域负责把散落的节点串成逻辑顺序,struct Node { int data; Node* next; }; 是标准形态。指针域是链表区别于数组一切特性的根源。
排除法:D 选下标与长度的人把数组的配套概念搬进了节点;C 选键与值的人描述的是映射结构;B 选地址与编号的人把「节点自身的地址」与「自身内容」混为一谈。
关于单链表的头指针 head 与第一个节点的关系,正确的是( )。
考点:头指针与首节点(A2)。
(A2)考点:头指针与首节点——head 存首节点地址,为 NULL 即空表。
解析:本题考查头指针与首节点。head 是进入链表唯一的入口:它指着谁,谁就是第一个节点;它谁都不指(NULL),链表就是空的。头插删头等操作都会改动 head 的指向。
排除法:A 以为 head 本身是节点的人没分清「指针」与「被指者」,head 没有 data 域;D 以为 head 存长度的人把它当成了长度计数器;C 以为头指针永不能变的人忘了头插法的本质就是改 head。
链表各节点在内存中的存放方式是( )。
考点:物理存储不连续(A3)。
(A3)考点:物理存储不连续——节点按需分散在内存各处,靠指针连成逻辑链。
解析:本题考查物理存储不连续。每个 new Node 申请到哪里全凭分配器安排,两个逻辑上相邻的节点物理上可能相距很远;「下一个在哪」的信息完全由指针域携带,与内存位置无关。
排除法:C 选连续内存的人把数组的存储形态错安给链表;D 选按地址从大到小的人虚构了不存在的排列规则;B 选同一字节的人违反了基本物理常识。
与固定长度的数组相比,链表在数据规模方面的特点是( )。
考点:长度动态变化(A4)。
(A4)考点:长度动态变化——链表按需增删节点,不预估容量、不搬移其他元素。
解析:本题考查长度动态变化。要存就 new 一个挂上去,不要就摘下来 delete,链表长度随时伸缩;数组则要在定义时定长,装满后想扩容得整体搬家。动态性是链表的核心卖点。
排除法:B 选长度固定的人把数组的限制错记成链表的;A 选最多一百个的人把教学示例的规模当成了上限;D 选自动翻倍的人把 vector 的扩容策略错安在裸链表上。
单链表最后一个节点的指针域通常存放( )。
考点:尾节点空标记(A5)。
(A5)考点:尾节点空标记——尾节点的 next 存 NULL,表示链到此为止。
解析:本题考查尾节点空标记。NULL 是「没有下一个」的公认记号,一切遍历循环都以 p != NULL 收尾。它不占额外空间、判断零成本,是链表收尾的标准设计。
排除法:A 选存自身地址的人描述的是循环链表的单节点特例,不是常规单链表;B 选存头节点地址的人描述的是循环链表;D 选存数值 的人把「下标记号」的用法错安到指针域上——指针域只能放地址或空。
判断一个单链表当前没有任何节点,依据是( )。
考点:空链表(A6)。
(A6)考点:空链表——头指针为 NULL 即链表无任何节点。
解析:本题考查空链表。链表没有独立的长度字段,空与不空只看 head:head == NULL 空表,反之非空。所有链表操作动第一步都该先过这道判空闸。
排除法:C 选 号下标的人把数组空判(下标越界)搬了过来;D 选数据域为 的人混淆了「节点存在但值为 0」与「没有节点」;A 选长度变量为 的人凭空造出了一个不存在的字段。
按指针连接方式分类,链表的三种基本形态是( )。
考点:三种链表形态总览(A7)。
(A7)考点:三种链表形态——单向、双向、循环是链表的三种基本形态。
解析:本题考查三种链表形态。单向每节点一根向后的指针;双向再加一根向前的;循环把尾接回头形成圈。三者可以组合(如双向循环链表),本卷 D、E 组分别展开后两种。
排除法:A 选顺序、索引、散列的人把存储结构的分类学(索引结构、散列结构)错当链表形态;D 选大中小的人按规模分类驴唇不对马嘴;C 选静态、动态、虚的人混入了不规范的叫法——静态链表只是实现方式,虚链表不存在。
链表与数组最本质的区别是( )。
考点:链表与数组对比(A8)。
(A8)考点:链表与数组对比——数组连续内存下标直达,链表指针串联顺藤摸瓜。
解析:本题考查链表与数组对比。一句话定位:数组「找得快、插删慢」,链表「插删快、找得慢」。根源就在存储方式:连续带来 定位也带来 搬移,分散带来自由增删也带来只能顺序走。
排除法:B 选只能存整数的人给数组加了不存在的限制;D 选链表永远省内存的人忘了每个节点要多付一个指针域的开销;A 选数组元素不能改的人把「定长」误解成了「只读」。
单链表从头到尾访问每个节点,能采用的移动方式是( )。
考点:遍历(B1)。
(B1)考点:遍历——从 head 出发沿 next 逐节点走到 NULL。
解析:本题考查遍历。链表遍历的固定节奏:for (Node* p = head; p != NULL; p = p->next)。方向唯一(向后)、步长唯一(一步),任何「跳着走」「回头走」的想象都不成立。
排除法:C 选按下标跳转的人忘了链表没有下标随机访问;B 选先跳尾部往回走的人把双向/循环链表的特例当成通则;A 选任选节点向两边扩展的人描述的是双向链表且起点任意,单链表做不到。
在单链表中查找值等于 x 的节点,最少必须做的事是( )。
考点:查找(B2)。
(B2)考点:查找——单链表查找只能从首节点逐个比较。
解析:本题考查查找。链表无序无下标,二分的「看中间」都做不到——连中间都定位不了;唯一手段就是从头顺着比。这是 A5 组「链表找得慢」的具体来源。
排除法:B 选用二分法的人忘了二分需要随机访问中点,链表不具备;D 选直接算下标的人把数组的定位方式错安过来;C 选先排序再找的人本末倒置——排序本身就要先遍历,代价远超一次顺序查找。
头插法插入新节点 t 的两步核心操作是( )。
考点:头插法(B3)。
(B3)考点:头插法——先接后继再改头:t->next = head; head = t。
解析:本题考查头插法。两步顺序是铁律:先把新节点接到旧首节点前面,再让 head 认新节点。头插法每次 ,代价是输入顺序与链上顺序恰好相反(B8)。
排除法:D 选先改 head 再接后继的人会把旧链整个丢掉——t->next = head 此时接的是 t 自己;A 选 t->next = NULL 的人把新节点接到了虚空;C 选只改 head->next 的人把新节点插到了第二个位置而非头部。
用尾插法把新节点接在链表末尾,需要的条件或做法是( )。
考点:尾插法(B4)。
(B4)考点:尾插法——知道尾指针即可一步接尾,否则要从头走到尾。
解析:本题考查尾插法。尾部插入的钥匙是「找到尾」:维护尾指针 tail 时 tail->next = t 即成;没有尾指针就得从头走 。I2 是带尾指针的标准建链代码。
排除法:C 选把新节点指头的人描述的是循环链表尾插的一半;B 选直接 head = t 的人做成了头插且丢了旧链;D 选不需要尾部信息的人低估了「找尾」的成本。
在第 个节点之后插入新节点 t,正确的指针操作顺序是( )。
考点:指定位置插入(B5)。
(B5)考点:指定位置插入——先接新节点的后继,再让前驱指向新节点。
解析:本题考查指定位置插入。t->next = p->next; p->next = t; 顺序不能反:第一句把后继先存进新节点,第二句才安全地让前驱改口。反过来的灾难见 P1。四行代码两步走,是链表最核心的手工操作。
排除法:C 选先改前驱的人会让 t->next = p->next 拿到 t 自己,原后继失联;B、A 选只改一处的人都会造成断链或丢节点——两处各管一根指针,缺一不可。
从单链表中删除 p 的后继节点 q,正确的操作是( )。
考点:删除节点(B6)。
(B6)考点:删除节点——前驱跨接后继,再释放被删节点。
解析:本题考查删除节点。p->next = q->next; 让 p 直接指到 q 的后面,q 从链上摘除,最后 delete q 归还内存。跨接在前、释放在后,顺序颠倒就成了释放后使用(P4)。
排除法:C 选直接释放的人让链在 q 处断裂,后面整段丢失;A 选 q->next = p->next 的人把赋值方向写反,链结构完全错乱;D 选清零数据域的人只抹了内容,节点还挂在链上占着位置。
访问单链表的第 个节点,时间代价是( )。
考点:访问第 k 个(B7)。
(B7)考点:访问第 k 个——链表定位第 k 个节点要逐步走,O(k)。
解析:本题考查访问第 k 个。链表没有下标直达能力,想到达第 个节点只能从第一个走 步;这也是链表 «数组 在随机访问上的差距来源。数组同样的访问是 。
排除法:C 选一步直达的人把数组的下标访问错安过来;A 选 的人给它安上了二分的复杂度;B 选与 无关常数的人同样没抓住「顺藤摸瓜」的本质。
依次读入 :用头插法建链后从前往后输出,与用尾插法建链后从前往后输出,分别得到( )。
考点:头插尾插输出顺序(B8)。
(B8)考点:头插尾插输出顺序——头插逆序、尾插正序。
解析:本题考查头插尾插输出顺序。头插把每个新元素放到最前面,先来的被压到后面,输出倒序 4 3 2 1;尾插按序接尾,输出正序 1 2 3 4。想要「按读入顺序输出」选尾插,想要「逆序」头插白送。
排除法:A 两组答案选反的人把两种插法的作用记反;B 选两组都正序的人没见过头插的倒序效果;D 选两组都倒序的人同样混淆——尾插恰恰保持原序。
在已经定位到插入或删除位置的前提下,单链表插入、删除节点本身的时间复杂度是( )。
考点:链表插入删除复杂度(C1)。
(C1)考点:链表插入删除复杂度——定位后插删本身只改指针,O(1)。
解析:本题考查链表插入删除复杂度。前提「已定位」很关键:位置拿到了,插入删除就是改两根指针的活,与链表多长无关。若把「从头找位置」算上则整体是 ——快的只是插删动作本身。
排除法:B 答 搬移的人把数组的代价错安过来,链表不动任何其他节点;C 答 的人给它安上了二分的量级;A 答 的人凭空夸大了两个数量级。
在数组中部插入或删除一个元素,平均需要搬移的元素个数约为( )。
考点:数组插入删除复杂度(C2)。
(C2)考点:数组插入删除复杂度——中部插删平均搬移约一半元素,O(n)。
解析:本题考查数组插入删除复杂度。中部插入要把后半段整体后挪一格,删除要前挪补洞,平均搬移 个元素,量级 。这是链表在密集插删场景的核心优势来源。
排除法:C 答只需 次操作的人只看到了「覆盖一个格子」,忘了后面整排要挪;D 答 的人把插删与二分查找的量级混淆;A 答直接覆盖的人描述的是「替换值」,不是插入删除。
单链表中查找某个值,时间复杂度是( )。
考点:链表查找复杂度(C3)。
(C3)考点:链表查找复杂度——链表查找只能顺序扫描,O(n)。
解析:本题考查链表查找复杂度。链表不支持随机访问,看不了中间,跳不了步,查找只能从头一个个比,最坏 次。链表「插删快、查找慢」的账要两头算。
排除法:C 答 的人以为有直达手段;D 答 的人忘了二分的前提(有序+随机访问)链表一条都不占;B 答 的人把排序的量级错安在查找上。
不带尾指针的单链表,「头部插入」与「尾部插入」的时间代价是( )。
考点:头尾操作对比(C4)。
(C4)考点:头尾操作对比——单链表头插 O(1),尾插无尾指针则 O(n)。
解析:本题考查头尾操作对比。头部永远是 head 现成指着,插入删除都是 ;尾部若不维护尾指针,每次都要从头走到底。所以「频繁尾插」要么带尾指针,要么改用循环链表带尾指针(E4)。
排除法:A 答两者都 的人默认了尾指针的存在,普通单链表没有;B 答头 尾 的人把方向整个弄反;C 答两者都 的人低估了头部操作的便捷。
在序列中部频繁插入删除元素,链表与数组相比( )。
考点:中部插入对比(C5)。
(C5)考点:中部插入对比——链表改指针即插,数组要搬后半段。
解析:本题考查中部插入对比。同样「在第 100 个元素后插一个」:链表定位后改两根指针完事;数组要把后面全部元素逐个后挪。插删密集的场合链表显著占优(前提是位置信息已掌握或顺序维护)。
排除法:C 选数组占优的人把「下标直达」的好处错用在插删场景——直达帮不了搬移;A 选代价完全相同的人没做过两边的操作数对比;B 选两者都不适合的人下了过重的结论,链表正是为此而生。
关于链表与数组的内存布局,正确的是( )。
考点:内存连续性(C6)。
(C6)考点:内存连续性——数组连续一整块,链表分散且每节点多付一个指针域。
解析:本题考查内存连续性。数组要一次申请到位才能保证连续;链表节点零散分配、按需领取,代价是每个节点多存指针(双向还要两根)。内存形态差异是两者一切行为差异的根源。
排除法:A 选都连续的人没理解链表的分配方式;D 选链表更省内存的人忘了指针域开销——小数据场景链表反而更费;C 选数组元素靠指针连接的人把链表的连接方式错安给数组。
双向链表每个节点包含的三个部分是( )。
考点:三域节点(D1)。
(D1)考点:三域节点——双向链表节点含数据、前驱指针、后继指针。
解析:本题考查三域节点。struct DN { int d; DN* prev, *next; }; 比单链表多出的 prev 是「往回走」的全部资本,双向链表的能力全部由此生长。
排除法:B 选下标与长度的人还是数组的配套概念;A 选两个数据域的人把 prev 的用途完全理解错;D 选头尾指针的人把整表级别的指针错安进了单个节点。
在双向链表中,节点 p 的前驱与后继分别通过( )访问。
考点:前驱后继(D2)。
(D2)考点:前驱后继——p->prev 与 p->next 分别访问前驱与后继。
解析:本题考查前驱后继。命名上 prev/next 是通用习惯(也有用 l/r 或 front/back 的,读代码认语义即可);有了前驱指针,「找上一个」从 降为 。
排除法:B 选 left/right 的人把特定代码库的命名当成了唯一标准;A 选 p-1/p+1 的人把指针运算错用在链表上——相邻节点地址不连续;C 选函数形式的人把成员访问语法记错。
在双向链表节点 p 与其后继 q 之间插入新节点 t,至少要改动的指针数是( )。
考点:双向插入(D3)。
(D3)考点:双向插入——p 与 q 之间插入 t 要改 4 根指针。
解析:本题考查双向插入。四方各改一根:t->prev = p; t->next = q; p->next = t; q->prev = t;。漏任何一根都会造成方向不一致(P6)。对比单链表的 2 根,这就是双向的代价。
排除法:C 答 根的人只改了 t 自己,p 与 q 还互相指着,t 被晾在链外;B 答 根的人漏得更多;A 答 根的人多算了两根不存在的指针。
从双向链表中删除节点 p(非首非尾),正确的操作是( )。
考点:双向删除(D4)。
(D4)考点:双向删除——前后邻居互指,再释放自己。
解析:本题考查双向删除。p->prev->next = p->next; p->next->prev = p->prev; 两句让前驱与后继直接牵手,p 从两个方向的链上同时消失,最后释放。这是双向链表最舒展的操作——单链表删节点还得先找前驱。
排除法:A 选只释放 p 的人让链在 p 处两个方向同时断裂;B 选只改一处的人造成单向断链;D 选 p->next = p->prev 的人把「让邻居互指」写成了「自己乱指」。
要从尾部向头部逐个访问节点,双向链表与单链表相比( )。
考点:逆序遍历(D5)。
(D5)考点:逆序遍历——双向链表沿 prev 回走,单链表做不到。
解析:本题考查逆序遍历。从 tail 出发 p = p->prev 一步步回到头,与正向遍历一样自然。单链表的指针只有向后的单行道,想倒着走只能把整条链先逆置(J4)或借助数组缓存。
排除法:C 选两者同样方便的人忘了单链表指针的单向性;A 选单链表更快的人方向都没弄清;B 选双向无法反向的人正好说反——反向恰是双向存在的意义。
双向链表用每个节点多一个指针的代价换来的主要好处是( )。
考点:双向优势与代价(D6)。
(D6)考点:双向优势与代价——多一个指针换「任意节点直接找前驱」。
解析:本题考查双向优势与代价。有了 prev,删除给定节点不必先从头找前驱,反向遍历随取随用;代价是每个节点多 8 字节指针与插入删除时多改两根指针。要不要双向,看「找前驱」的频率高不高。
排除法:C 选查找 的人高估了双向的能力——找值仍要扫;B 选内存更少的人正好说反;D 选长度翻倍的人把不相干的性质扯了进来。
链表代码里设置哑节点(不存真实数据的附加节点)的主要目的(在后文中「哑节点」统一指此概念)是( )。
考点:哑节点与哨兵(D7)。
(D7)考点:哑节点与哨兵——附加一个不存数据的节点,统一头部特判。
解析:本题考查哑节点与哨兵。链表最烦的边界是「删的恰好是头节点」「插到最前面」——此时 head 本身要变。加一个永远在首位的哑节点后,真实节点统统「有前驱」,删除插入统一按普通节点处理。它自己不参与数据。
排除法:D 选提速查找的人把边界简化误当性能优化;B 选存放数据的人没抓住「哑」字——它不装数据;A 选标记损坏的人把它当成了故障标志,恰恰相反,它是让代码更稳的构件。
单循环链表区别于普通单链表的特征是( )。
考点:定义尾指头(E1)。
(E1)考点:定义尾指头——循环链表尾节点的 next 指回头节点。
解析:本题考查定义尾指头。把单链表的 NULL 换成 head,链就闭合成圈:从任何点出发都能走遍全圈。它没有天然的「终点标记」,遍历终止的判定方式随之改变(E2)。
排除法:C 选每节点两根指针的人描述的是双向链表;B 选尾存 的人还是单链表的思维;D 选头有 prev 的人描述的是双向循环链表的一半特征。
遍历单循环链表一圈,终止条件通常写成( )。
考点:遍历终止判断(E2)。
(E2)考点:遍历终止判断——循环链表以「回到起点」为终止。
解析:本题考查遍历终止判断。标准写法 p = head; do { 访问 p; p = p->next; } while (p != head);——先走再判,保证起点也被访问且恰好一圈。若先判后走用 while (p != head) 起步条件即为假,一个都访问不到。
排除法:D 选判 NULL 的人会把 E6 的死循环引来;C 选判 p->next == p 的人只适用于单节点自环的特例;B 选数满 步的人把教学示例的规模当成了循环条件。
约瑟夫问题: 个人围成一圈,从某人起报数,每报到 的人出列并由下一人继续。最适合自然模拟这一过程的数据结构是( )。
考点:约瑟夫环(E3)。
(E3)考点:约瑟夫环——循环链表天然模拟围圈报数出列。
解析:本题考查约瑟夫环。人围成圈 = 循环链表;报数 = 指针沿 next 移动;出列 = 删节点。圈的形态在删除过程中始终保留,模拟直白。K6 与 O4 分别考出列顺序与幸存者。数组也能做(模下标),但每次删除要搬移。
排除法:A 选普通数组的人也能做但删除的搬移代价与「圈」的直观性都吃亏,题干问的是「最适合自然模拟」;D 选栈的人方向反了;B 选二叉树的人引入了完全无关的结构。
带尾指针 tail 的单循环链表,只凭 tail 就能在 内完成的操作是( )。
考点:带尾指针循环链表(E4)。
(E4)考点:带尾指针循环链表——凭 tail 一步拿到尾与头,头尾操作全 O(1)。
解析:本题考查带尾指针循环链表。循环结构里 tail->next 就是头节点:尾插先接 t(t->next = tail->next; tail->next = t; tail = t;),头插经由 tail->next 改头。两个方向的 操作使它成为「既要头又要尾」场景的利器。
排除法:C 选查找任意值的人高估了它的能力——查找照样要扫圈;A 选访问正中间的人忘了定位中点仍要逐个走;D 选排序的人把组织结构的便利错当排序能力。
单循环链表的一个独特能力是( )。
考点:任一节点遍历全表(E5)。
(E5)考点:任一节点遍历全表——循环链表从任何节点出发都能走完全圈。
解析:本题考查任一节点遍历全表。圈没有断点,入口选谁都行:从任意节点出发沿 next 走,回到出发点时全表已扫一遍。单链表从中途某节点出发,只能访问「它及其后继」,前面的够不着。
排除法:A 选一步跳转的人把遍历能力误当随机访问;C 选不需要指针的人忘了圈就是靠指针闭合的;B 选自动有序的人把结构与有序性混为一谈。
遍历单循环链表时,若把终止条件误写成与单链表相同的 p != NULL,后果是( )。
考点:循环条件死循环(E6)。
(E6)考点:循环条件死循环——循环链表没有 NULL,判 NULL 永远为真。
解析:本题考查循环条件死循环。单链表的终止条件建立在「尾指 NULL」上;循环链表把这个标记取消了,p != NULL 成了永真式,循环停不下来。正确终止见 E2 的「回到起点」。
排除法:D 选恰好正确的人忽略了程序根本停不下来;A 选编译报错的人把运行期逻辑错误当成编译期检查;C 选自动断开的人给链表虚构了自我修复能力。
用单链表实现栈,入栈与出栈都应对链表的哪一端操作( )。
考点:链表实现栈(F1)。
(F1)考点:链表实现栈——头部即栈顶,头插为入栈、删头为出栈。
解析:本题考查链表实现栈。栈只在一端进出,选头部最划算:入栈就是头插(),出栈就是删头节点(),head 天然扮演栈顶指针。若选尾部,删尾要先找前驱,单链表做不到 。
排除法:D 选尾部的人没算删尾的账——单链表删尾要走到倒数第二个;A 选正中间的人把栈的两端性质完全丢弃;B 选哪端都一样的人没比较过两端操作的代价。
用单链表实现队列,正确的指针配置是( )。
考点:链表实现队列(F2)。
(F2)考点:链表实现队列——头指针管出队、尾指针管入队,两端都 O(1)。
解析:本题考查链表实现队列。队列先进先出:从尾入(tail->next = t)、从头出(删头更新 head),配齐两个指针即可全 。O1 是这套配置的操作执行题。只留一个指针必有一端退化成 。
排除法:D 选只留头指针的人每次入队要走全链;C 选只留尾指针的人每次出队要走全链;A 选不能用链表的人过度设限——链表恰是队列的经典实现之一。
哈希表处理冲突的链地址法,把冲突元素组织成链表挂在对应桶上,这里链表承担的角色(此处仅作链表应用了解)是( )。
考点:链地址法挂链(F3)。
(F3)考点:链地址法挂链——桶内冲突元素用链表组织,按需增长插删灵活。
解析:本题考查链地址法挂链。哈希桶里会撞进来几个元素事先不定,链表「长度随便长、插入只挂指针」的性质正合需求。此处在纲外仅作链表应用了解,哈希表的系统知识属提高级。
排除法:D 选让哈希函数更快的人把存储结构与函数计算混为一谈;A 选防止冲突的人夸大了它——冲突照样发生,只是被有序收纳;B 选让桶有序的人没注意链地址法的链表通常按插入顺序挂,无序。
数据总量事先无法估计、且会频繁增删,链表与定长数组相比更合适的原因是( )。
考点:数据量未知选链表(F4)。
(F4)考点:数据量未知选链表——按需逐个申请,不预估容量不整体搬迁。
解析:本题考查数据量未知选链表。定长数组赌小了装不下、赌大了浪费;链表来一个节点申请一个,规模弹性极大。代价是查找定位慢——选型时配合访问模式一起权衡。
排除法:D 选查找更快的人正好把短板说成长处;A 选支持随机访问的人与事实相反;C 选不需要内存的人说了句不可能的话——每个节点都要实打实的内存。
维护一条有序单链表,插入新值时从头找第一个比它大的节点,插到其前面。这样维护的好处是( )。
考点:有序链表插入维护(F5)。
(F5)考点:有序链表插入维护——每次定位到第一个更大者前插入,链始终有序。
解析:本题考查有序链表插入维护。插入时从头找「第一个比新值大的节点」,插到它前面(J8、O3 的代码形态);链长 时每次插入定位 ,但序列随时整体有序可输出。对比数组版有序维护的搬移,链表版只改指针。
排除法:C 选不比较任何值的人没想过「插到哪」本身就需要比较;B 选自动排序的人把希望寄托给了不存在的魔法;D 选查找 的人把「随时有序」误当「随时直达」。
快慢指针判断链表是否有环的原理是( )。
考点:快慢判环(G1)。
(G1)考点:快慢判环——慢一步快两步,有环必相遇,无环快指针到 NULL。
解析:本题考查快慢判环。无环时快指针一路领先先撞 NULL;有环时两者进入圈内,每轮快指针比慢指针多走一步、距离逐步缩短,必在某点重合。K3、M1 是执行追踪。这是笔试面试双高频的经典。
排除法:B 选快指针先走完的人没想过有环时根本没有「完」;A 选同速的人让两指针永远保持初始距离,遇不上;D 选必须三步的人把步长与相遇的关系理解错——两步恰好保证每轮追近一步,三步可能跨越错过(存在不相遇的环长组合)。
快慢指针找链表中间节点,指针的走法是( )。
考点:快慢找中间(G2)。
(G2)考点:快慢找中间——快两步慢一步,快到尾时慢在中点。
解析:本题考查快慢找中间。快指针速度是慢的两倍,快走完全程时慢恰好走了一半。奇数长度停在正中,偶数长度停在后半第一个(M2 实测 节点停 )。一次遍历拿到中点,不用先数长度再走一遍。
排除法:B 选慢两步快一步的人把配比弄反,慢的反而快;D 选从两端相向而行的人把数组双指针的套路错安到只能单向走的链表上;A 选快指针先到中点等着的人没理解「等」本身就是另一种提前定位。
一次遍历求倒数第 个节点的双指针技巧是( )。
考点:倒数第 k(G3)。
(G3)考点:倒数第 k——快指针先行 k 步,两指针同速,快到 NULL 慢就位。
解析:本题考查倒数第 k。先让快指针领先 步,随后同速前进,两指针间距恒为 ;快指针触到 NULL 时,慢指针离链尾恰好 个位置——正是倒数第 。一次遍历解决,M3 还把删除动作接了上去。
排除法:B 选慢指针先走的人把「领先」与「滞后」弄反,慢指针跑到了参照系外;D 选反向而行的人又用了双向链表才有的能力;C 选先数长度再走的人用了两次遍历,能做但不是双指针一次遍历的解法。
「当前指针 p 与后继指针 q 同速前进」的双指针遍历,相比单指针的好处(在删除场景中最明显)是( )。
考点:双指针遍历(G4)。
(G4)考点:双指针遍历——前驱当前同速推进,删除时前驱现成。
解析:本题考查双指针遍历。p 与 q=p->next 同速前进,任何时刻 p 都是 q 的前驱;要删 q 时 p->next = q->next 直接动手,不必再从头找前驱。单指针删除都要付一次回头路,这就是双指针的收益。
排除法:C 选速度快一倍的人把「两个指针」误解成「速度翻倍」;D 选省一半内存的人凭空想象了不存在的节省;B 选跳过偶数节点的人把另一个技巧(按 step 跳)错安在本技巧上。
快指针一次走两步的循环条件要写成 while (fast && fast->next),两个条件的作用是( )。
考点:快指针步长条件(G5)。
(G5)考点:快指针步长条件——fast 与 fast->next 两个条件各防一种踩空。
解析:本题考查快指针步长条件。走两步是两次「取 next」:fast 本身可能是 NULL(第一步就踩空),fast->next 可能是 NULL(第二步踩空),两个条件分别守住两步。缺第二个条件在奇数长度链上必崩(M5 展开)。
排除法:A 选两条件重复的人没推过奇数链的收尾;B 选第一个条件加速的人给条件安了不相干的职能;C 选让慢指针变快的人把条件的保护对象弄错——它保护的是快指针自己。
数组模拟链表(静态链表)用两个平行数组 data[] 与 nxt[] 存链,其中 nxt[i] 存放( )。
考点:静态结构与 next 数组(H1)。
(H1)考点:静态结构与 next 数组——nxt[i] 存后继下标,-1 为链尾。
解析:本题考查静态结构与 next 数组。把「指针」换成「下标」:data[i] 是内容,nxt[i] 是下一节点的数组下标, 表示到头。逻辑与动态链表完全同构,物理上却是纯数组,不需要 new/delete。
排除法:B 选存数据值的人把两个数组的分工弄混;C 选存总长度的人把全局信息塞进了每格;A 选存真实地址的人没理解「模拟」的本质——用下标顶替地址。
静态链表的「头指针」实际是( )。
考点:头指针是下标(H2)。
(H2)考点:头指针是下标——静态链表的 head 是存首节点下标的整型变量。
解析:本题考查头指针是下标。动态链表的入口是 Node*,静态链表把入口降格为 int head——存的是首节点的下标号。空表用 表示(与链尾标记一致)。概念换壳,思想不变。
排除法:C 选 Node* 指针的人没换掉动态思维;D 选数组长度的人把入口与规模混淆;B 选固定 的人忘了首节点未必放下标 ——数组哪个位置空闲就用哪个(L 组的链首元素就在下标 只是数据安排的巧合)。
静态链表在第 号节点后插入新节点(放在空闲下标 处),需要做的赋值是( )。
考点:静态插入(H3)。
(H3)考点:静态插入——先接后继下标再改前驱,与动态版同构。
解析:本题考查静态插入。nxt[j] = nxt[i]; nxt[i] = j; 与动态链表「先接后继再改前驱」一字不差地对应,只是指针换成下标赋值。顺序同样不能反,反了同样丢后继。
排除法:D 选先改前驱的人让 nxt[j] = nxt[i] 拿到 自己,原后继失联;C 选只改一句的人要么断链要么没真正挂上;A 选交换 data 的人把插入做成了换值。
静态链表中删除第 号节点的后继 ,正确的操作是( )。
考点:静态删除(H4)。
(H4)考点:静态删除——nxt[i] = nxt[j] 跨过 j 即逻辑删除。
解析:本题考查静态删除。让前驱直接指向被删者的后继, 从链上消失。注意静态链表常常不真清空格子——把 号位挂回空闲链即可复用;是否回收是工程选择,逻辑删除一步到位。
排除法:D 选搬移所有元素的人把数组的删除代价错安过来;A 选清零 data 的人节点还挂在链上会被遍历到;C 选清零 nxt 的人把 j 的后继信息毁了但 j 可能还在链上。
静态链表与动态链表相比的固有限制是( )。
考点:容量受限(H5)。
(H5)考点:容量受限——静态链表节点数不能超过数组容量。
解析:本题考查容量受限。数组开多大,节点上限就是多大,超了就无处安放;动态链表按需申请理论上只受系统内存限制。开数组前估算规模(或开足够大的安全值)是静态链表的使用纪律。
排除法:D 选不能插删的人与事实相反——改下标即可;C 选只能存字符的人把元素类型与存储方式混淆;B 选查找从尾部开始的人虚构了不存在的规则——查找永远从头下标出发。
01struct Node { int data; Node* next; }; 02int main() { 03 Node* head = NULL; 04 for (int i = 1; i <= 4; i++) { 05 Node* t = new Node; 06 t->data = i; 07 t->next = head; 08 head = t; 09 } 10 for (Node* p = head; p != NULL; p = p->next) 11 cout << p->data << " "; 12 return 0; 13}
输出是( )。
考点:头插建链(I1)。
(I1)考点:头插建链——依次头插 1 到 4,链上顺序倒置为 4 3 2 1。
解析:本题考查头插建链。每来一个新数都插到最前: 进链为 1; 头插变 2 1; 变 3 2 1; 变 4 3 2 1。头插两步 t->next = head; head = t; 逐轮执行,输出自然逆序。
排除法:A 答 1 2 3 4 的人按尾插的结果理解了头插;C、B 答 1 4 2 3 与 4 1 3 2 的人对两步接链的次序理解错乱,头插的次序是确定性的不会随机。
01struct Node { int data; Node* next; }; 02int main() { 03 Node* head = NULL; 04 Node* tail = NULL; 05 for (int i = 1; i <= 3; i++) { 06 Node* t = new Node; 07 t->data = i; 08 t->next = NULL; 09 if (head == NULL) head = t; 10 else tail->next = t; 11 tail = t; 12 } 13 for (Node* p = head; p != NULL; p = p->next) 14 cout << p->data << " "; 15 return 0; 16}
输出是( )。
考点:尾插建链(I2)。
(I2)考点:尾插建链——带尾指针依次接尾,保持原序 1 2 3。
解析:本题考查尾插建链。tail 始终指最后节点:空表时新节点既是头也是尾;其后每次 tail->next = t 接上再挪 tail。链上顺序与输入一致,输出 1 2 3。
排除法:A 答 3 2 1 的人把头插的倒序效果错安过来;C、B 答 1 3 2 与 2 3 1 的人对空表分支(首节点要单独认头)理解有洞,尾插法一旦首节点接错位就全乱。
01// 链表已建为 4 -> 6 -> 2 -> NULL(head 指向 4) 02int s = 0; 03for (Node* p = head; p != NULL; p = p->next) 04 s += p->data; 05cout << s;
输出是( )。
考点:遍历求和(I3)。
(I3)考点:遍历求和——沿 next 累加 data,4+6+2=12。
解析:本题考查遍历求和。循环从 head 走到 NULL,把每个节点的 data 加进 s:。链表求和与数组求和同构,只是「下一元素」从下标自增换成 p = p->next。
排除法:A 答 的人只算了首节点,以为循环不推进;D 答 的人只算了中间节点;B 答 的人数的是节点个数不是和。
01// 链表为 1 -> 2 -> 3 -> 4 -> 5 -> NULL 02int n = 0; 03for (Node* p = head; p != NULL; p = p->next) 04 n++; 05cout << n;
输出是( )。
考点:统计节点数(I4)。
(I4)考点:统计节点数——遍历计数,五节点得 5。
解析:本题考查统计节点数。链表没有现成的长度字段,长度只能现走现数:每过一个节点 n++,五节点输出 。这也是快慢指针找中点前「先数长度」做法的基础步骤。
排除法:C 答 的人差一,多半把循环条件写成了 p->next != NULL 提前一步停(P3);A 答 的人把求和的结果与计数混淆;D 答 的人以为只统计首节点。
01// 链表为 3 -> 9 -> 5 -> 7 -> NULL 02int mx = head->data; 03for (Node* p = head->next; p != NULL; p = p->next) 04 if (p->data > mx) mx = p->data; 05cout << mx;
输出是( )。
考点:找最大值(I5)。
(I5)考点:找最大值——打擂台沿链比大小,9 胜出。
解析:本题考查找最大值。擂主初值取首节点 ,随后 攻擂成功、、 挑战失败,输出 。链表版打擂台与数组版逻辑相同,注意擂主初值不能凭空设 (负数链会错),取首节点最稳。
排除法:A 答 的人以为初值即终值;C 答 的人把最后看到的较大值当成了最大值;D 答 的人把求和的结果错当最大值。
01// 链表为 2 -> 5 -> 3 -> 8 -> 6 -> NULL 02int x = 5, cnt = 0; 03for (Node* p = head; p != NULL; p = p->next) 04 if (p->data > x) cnt++; 05cout << cnt;
输出是( )。
考点:条件统计(I6)。
(I6)考点:条件统计——遍历中按条件计数,大于 5 的有 2 个。
解析:本题考查条件统计。链上数据 ,大于 的只有 与 ,输出 。注意 本身不大于 ,等于不算。
排除法:A 答 的人把链长当成了满足条件的个数;B 答 的人把等于 的也算进去;C 答 的人算的是满足条件元素之和而非个数。
01// 链表为 1 -> 2 -> 3 -> NULL 02while (head != NULL) { 03 cout << head->data << " "; 04 Node* t = head; 05 head = head->next; 06 delete t; 07}
输出是( )。
考点:清空释放(I7)。
(I7)考点:清空释放——先存后继再删当前,输出 1 2 3 且无泄漏。
解析:本题考查清空释放。循环体三步:记下 t、head 前移、delete t。每删一个先输出其值,依次 1 2 3。关键在于先取后继再删——删完再取就是释放后使用(P4)。
排除法:B 答 3 2 1 的人以为删除从尾部开始,释放永远从头走;C 答 1 的人以为只删首节点;D 答无输出的人没看到循环里的 cout。
01Node* head = NULL; 02if (head == NULL) cout << "empty"; 03else cout << head->data;
输出是( )。
考点:判空处理(I8)。
(I8)考点:判空处理——空表输出 empty,避免解引用 NULL。
解析:本题考查判空处理。head == NULL 分支先接住空表情形,输出 empty;若不判空直接 head->data,就是对空指针解引用、当场崩溃(P5)。判空是链表代码的第一道防线。
排除法:B 答 的人以为空表的数据域默认为零——节点都不存在,何谈数据域;D 答崩溃的人没看到 if 已把空表接住;A 答编译错误的人把运行期问题当成了编译期检查。
// 链表为 1 -> 3 -> 5 -> NULL,p 指向值为 3 的节点 Node* t = new Node; t->data = 4; t->next = p->next; p->next = t; // 从 head 输出整条链
输出是( )。
考点:指定位置插入(J1)。
(J1)考点:指定位置插入——先接 t 的后继再改 p 的指向,得 1 3 4 5。
解析:本题考查指定位置插入。p 指向 :t->next = p->next 让 先抓住 ,p->next = t 让 改口认 ,链变 1 3 4 5。两步顺序(B5)是全程要点。
排除法:A 答 1 4 3 5 的人把新节点插到了 之前——那是另一种操作(改前驱指向),本题代码插在后;B 答 1 3 5 4 的人把接尾误当成追加;C 答 4 1 3 5 的人以为改了 head。
01// 链表为 1 -> 2 -> 3 -> 2 -> NULL 02Node* p = head; 03while (p->next != NULL && p->next->data != 2) 04 p = p->next; 05if (p->next != NULL) { 06 Node* t = p->next; 07 p->next = t->next; 08 delete t; 09} 10// 从 head 输出整条链
输出是( )。
考点:删除给定值(J2)。
(J2)考点:删除给定值——找前驱跨接删后继,删第一个 2 得 1 3 2。
解析:本题考查删除给定值。删除要改前驱的 next,所以循环找的是「值为 2 的节点的前驱」:p 停在 (其后继是第一个 ),跨接后链变 1 3 2——只删第一个匹配,第二个 安然无恙。
排除法:C 答 1 3 的人删了两个 ,但循环在第一次删除后就退出;D 答 1 2 3 的人以为删的是末尾的 ;A 答 2 3 2 的人以为删了头节点。
空链表上依次执行:头插 、头插 ,然后从 head 输出,结果是( )。
考点:头插执行追踪(J3)。
(J3)考点:头插执行追踪——空链头插 10 再头插 20,得 20 10。
解析:本题考查头插执行追踪。第一步后链为 10;第二步 插到 前面,链为 20 10。头插的两步对空链同样适用(t->next = NULL 即 head 的当前值),无需特判——这正是头插法的优雅之处。
排除法:D 答 10 20 的人没注意第二次也是头插;B、C 答 10 10 与 20 20 的人以为某次插入覆盖了旧值——两个节点都真实存在。
01// 链表为 1 -> 2 -> 3 -> 4 -> NULL 02Node* rev = NULL; 03while (head != NULL) { 04 Node* t = head->next; 05 head->next = rev; 06 rev = head; 07 head = t; 08} 09// 从 rev 输出整条链
输出是( )。
考点:逆置链表(J4)。
(J4)考点:逆置链表——头摘法逐个前插,1 2 3 4 翻成 4 3 2 1。
解析:本题考查逆置链表。三步循环:摘下原链头(存后继)、把它头插到新链 rev、继续。原链 1 2 3 4 逐个搬到 rev:1 → 2 1 → 3 2 1 → 4 3 2 1。 一步到位,不借助数组。
排除法:C 答 1 2 3 4 的人以为只是复制没翻转;D、A 答 1 4 3 2 与 2 3 4 1 的人对「摘头前插」的某一步执行顺序理解错,翻转是严格确定的全倒序。
// 链表为 5 -> 3 -> 8 -> 1 -> 6 -> NULL,删除第 2 个节点 Node* p = head; // p 指向第 1 个节点(5) Node* t = p->next; p->next = t->next; delete t; // 从 head 输出整条链
输出是( )。
考点:删除第 k 个(J5)。
(J5)考点:删除第 k 个——p 停在第 k-1 个跨接删除,得 5 8 1 6。
解析:本题考查删除第 k 个。删第 个要前驱(第 个,值 )跨接:p->next 从 改指 ,链变 5 8 1 6。若删的是第 个(头),得改 head 本身——这正是哑节点要解决的特例(D7)。
排除法:A 答 3 8 1 6 的人删成了头节点;D 答 5 3 1 6 的人删的是第 个;B 答 5 8 6 的人多删了一个。
01// 链表为 5 -> 3 -> 8 -> 1 -> 6 -> NULL 02int k = 3; 03Node* p = head; 04for (int i = 1; i < k; i++) 05 p = p->next; 06cout << p->data;
输出是( )。
考点:找第 k 个(J6)。
(J6)考点:找第 k 个——从头走 k-1 步,第 3 个值是 8。
解析:本题考查找第 k 个。p 从 head 起,循环 k-1 次每次 p = p->next,停在第 个节点,值为 。链 5 3 8 1 6 的第 个与数组下标 一一对应。
排除法:B 答 的人以为不用走步停在第 个;C 答 的人少走一步停在第 个;D 答 的人多走一步停在第 个。
01// 链表为 1 -> 3 -> 5 -> 7 -> NULL 02bool inc = true; 03for (Node* p = head; p->next != NULL; p = p->next) 04 if (p->data >= p->next->data) inc = false; 05cout << (inc ? 1 : 0);
输出是( )。
考点:判断递增(J7)。
(J7)考点:判断递增——逐对相邻比较,全部严格递增输出 1。
解析:本题考查判断递增。循环条件 p->next != NULL 保证每一对相邻节点 恰好比一次:、、 全过,inc 保持真输出 。写成 >= 是刻意把相等也判为非严格递增。
排除法:D 答 的人没逐对比较就臆断失败;A 答 的人输出成了尾节点值;B 答编译错误的人不了解指针条件完全合法。
01// 有序链表为 1 -> 3 -> 7 -> NULL,插入 5 02Node* p = head; 03while (p->next != NULL && p->next->data < 5) 04 p = p->next; 05Node* t = new Node; 06t->data = 5; 07t->next = p->next; 08p->next = t; 09// 从 head 输出整条链
输出是( )。
考点:有序插入(J8)。
(J8)考点:有序插入——找第一个不小于新值的前驱插入,1 3 7 插 5 得 1 3 5 7。
解析:本题考查有序插入。循环让 p 停在「next 值 ≥ 5」的第一个节点()上:3 的 next 是 , 不小于 ,停;两步插入后链变 1 3 5 7。条件用 < 保证相等时插在前面。
排除法:C 答 1 3 7 5 的人以为直接接尾;B 答 5 1 3 7 的人以为头插;A 答 1 5 3 7 的人对定位条件理解错插进了已排序段中间的错误位置。
双向链表 1 <-> 2 <-> 3,tail 指向值为 的节点,执行:
01for (DN* p = tail; p != NULL; p = p->prev) 02 cout << p->d << " ";
输出是( )。
考点:双向反向输出(K1)。
(K1)考点:双向反向输出——从尾沿 prev 回走,输出 3 2 1。
解析:本题考查双向反向输出。tail 指 ,p = p->prev 依次走到 、,再往前 prev 为 NULL 停止。反向遍历与正向完全对称,这是 prev 指针的直接兑现(D5)。
排除法:B 答 1 2 3 的人沿 next 正向走了;D 答 3 的人以为走一步就停,忘了循环条件是 p != NULL;C 答死循环的人没注意反向走到头节点后 prev 为 NULL 会自然终止。
循环链表节点值从某处起依次为 首尾相接,指针 p 当前指向值为 的节点。连续执行 次 p = p->next 后,p->data 是( )。
考点:循环走 n 步(K2)。
(K2)考点:循环走 n 步——5 节点循环链走 7 步,7 mod 5 = 2,落在值 3。
解析:本题考查循环走 n 步。循环链走一圈回原点,位置只看步数模链长:,从 出发跨过 、 两步到第三个节点值 。链上「走 n 步」类问题的通用心算就是模一圈。
排除法:D 答 的人以为节点值跟着步数涨,链上根本没有 ;A 答 的人少算一步停在了值 ;B 答 的人对模运算用错被除数。
01// 链表为 1 -> 2 -> 3 -> 4 -> 2(4 的 next 指回 2,成环) 02Node* slow = head; 03Node* fast = head; 04int meet = -1; 05while (fast != NULL && fast->next != NULL) { 06 slow = slow->next; 07 fast = fast->next->next; 08 if (slow == fast) { meet = slow->data; break; } 09} 10cout << meet;
输出是( )。
考点:判环代码(K3)。
(K3)考点:判环代码——快慢指针在环内相遇,meet 为 4。
解析:本题考查判环代码。慢指针路线 ,快指针路线 :第 轮慢在 、快也在 ,相遇记录 meet = 4。只要入环,两指针每轮净接近一步,相遇只是时间问题。
排除法:C 答 的人以为快指针能逃出圈——环里没有 NULL,逃不掉;A 答 的人把入环点当成了相遇点,相遇点取决于起步位置与追及过程;B 答死循环的人没看到 if (slow == fast) break 恰好接住相遇。
01struct DN { int d; DN* prev; DN* next; }; 02DN a = {1, NULL, NULL}, c = {3, NULL, NULL}; 03a.next = &c; c.prev = &a; 04DN b = {2, NULL, NULL}; 05b.prev = &a; 06b.next = &c; 07a.next = &b; 08c.prev = &b; 09for (DN* p = &a; p != NULL; p = p->next) 10 cout << p->d << " ";
输出是( )。
考点:双向插入执行(K4)。
(K4)考点:双向插入执行——四根指针逐行赋值后 1 与 3 之间接入 2。
解析:本题考查双向插入执行。逐行读代码:b.prev = &a 与 b.next = &c 先把新节点 的两根指向邻居;a.next = &b 与 c.prev = &b 再让邻居认回 ——四根各就各位后链为 。从头沿 next 遍历输出 1 2 3;反向沿 prev 也能得到 3 2 1,两个方向互为镜像。
排除法:C 答 2 1 3 的人以为新节点排到了最前;B 答 1 3 2 的人只改了 自己的两根、漏了 a.next 与 c.prev 的认回;D 答 1 3 的人以为插入没有生效——四根赋值已把 嵌入链中。
01struct Node { int data; Node* next; }; 02Node* head = NULL; 03Node* tail = NULL; 04for (int i = 1; i <= 3; i++) { 05 Node* t = new Node; 06 t->data = i; 07 if (head == NULL) { head = t; t->next = head; } 08 else { t->next = head; tail->next = t; } 09 tail = t; 10} 11Node* p = head; 12do { 13 cout << p->data << " "; 14 p = p->next; 15} while (p != head);
输出是( )。
考点:循环尾插(K5)。
(K5)考点:循环尾插——带尾指针循环链表建链代码读一圈输出。
解析:本题考查循环尾插。逐轮追踪: 时空表,head = t 且 t->next = head 成自环,tail = t; 时 t->next = head 接头、tail->next = t 接尾,尾指针前移; 同理——链成 的圈。do-while 从 head 起输出一圈恰为 1 2 3,回到起点即止。
排除法:A 答 3 2 1 的人把头插的倒序错安给尾插;B 答 1 2 3 1 的人多输出了一圈起点——do-while 的条件会在回到 head 时及时收住;C 答 2 3 1 的人起点选错,head 始终指 。
约瑟夫问题: 个人编号 到 围成圈,从 号起报数,报到 的人出列,下一人继续从 报起。出列顺序是( )。
考点:约瑟夫出列(K6)。
(K6)考点:约瑟夫出列——5 人报 3,出列顺序 3 1 5 2 4。
解析:本题考查约瑟夫出列。模拟: 报到 出列(); 报到 出列(); 报到 出列(); 报到 出列();剩 。圈上删除用循环链表最直观(E3),O4 追问幸存者。
排除法:C 答 的人只报了一轮就连续出列,忘了每次报数重新从 开始;D 答 的人没做任何报数;A 答 的人把顺序整个倒过来凑数。
静态链表数组如下,head = 0:
01int data[] = {7, 9, 3, 1, 5}; 02int nxt[] = {2, 4, 1, -1, 3};
从 head 沿 nxt 依次输出 data,结果是( )。
考点:沿 next 输出(L1)。
(L1)考点:沿 next 输出——head=0 走 nxt 得 7 3 9 5 1。
解析:本题考查沿 next 输出。链路由 nxt 勾勒:,对应 data 为 。注意节点在数组里的物理顺序(下标 )与链上顺序完全无关——静态链表考的就是这层「下标当指针」的翻译。
排除法:A 答 7 9 3 1 5 的人按数组物理顺序读,没跟 nxt 走;C 答 7 3 1 5 9 的人跟错一格;B 答 7 3 9 1 5 的人末尾两步走岔。
静态链表 data = {7, 9, 3, 1, 5},nxt = {2, 4, 1, -1, 3},head = 0。从 head 沿链把各节点的 data 累加,总和是( )。
考点:静态求和(L2)。
(L2)考点:静态求和——沿链累加 7+3+9+5+1=25。
解析:本题考查静态求和。遍历路径与 L1 相同,累加各 data:。静态链表的求和统计与动态版同构,for (int p = head; p != -1; p = nxt[p]) 是标准循环形态。
排除法:B 答 的人漏加了链上某个节点;A 答 的人输出成了节点个数;C 答 的人按数组物理顺序前四个数求和没走完整条链。
静态链表 data = {7, 9, 3, 1, 5},nxt = {2, 4, 1, -1, 3},head = 0。在值为 的节点(下标 )之后插入空闲下标 、值为 的新节点:先 nxt[5] = nxt[1],再 nxt[1] = 5。插入后从 head 输出的值序列是( )。
考点:静态插入(L3)。
(L3)考点:静态插入——下标 5 接进 9 之后,得 7 3 9 6 5 1。
解析:本题考查静态插入。nxt[5] = nxt[1] 让新节点先抓住原后继(下标 ),nxt[1] = 5 让 改口认新节点;链变 ,即 。与动态插入同构,先接后改(H3)。
排除法:B 答 7 3 9 5 1 6 的人把新节点接到了链尾;C 答 7 3 6 9 5 1 的人把新节点插到了 之前;D 答 6 7 3 9 5 1 的人以为插到了链首。
静态链表 data = {7, 9, 3, 1, 5},nxt = {2, 4, 1, -1, 3},head = 0。删除值为 的节点(下标 )的后继,即执行 nxt[1] = nxt[4]。删除后从 head 输出的值序列是( )。
考点:静态删除(L4)。
(L4)考点:静态删除——nxt[1] = nxt[4] 跨过 5,得 7 3 9 1。
解析:本题考查静态删除。(下标 )的后继是下标 (值 );执行 nxt[1] = nxt[4] 后 直接指向下标 (值 ), 被跨过,链变 。被删格子的 data 还在数组里,但链上再也走不到它。
排除法:D 答 7 3 9 5 1 的人以为没改动;B 答 7 3 1 的人把 也删了;A 答 9 5 1 的人把前半段丢了。
01int data[] = {7, 9, 3, 1, 5}; 02int nxt[] = {2, 4, 1, -1, 3}; 03int head = 0; 04int pos = -1; 05for (int p = head; p != -1; p = nxt[p]) 06 if (data[p] == 5) pos = p; 07cout << pos;
输出是( )。
考点:静态查找(L5)。
(L5)考点:静态查找——沿 nxt 链查找值 5,落到下标 4。
解析:本题考查静态查找。沿链逐站核对 data[p]: 值 、 值 、 值 、 值 命中,pos 记为 ;随后 nxt[4]=3, 值 不中,nxt[3]=-1 循环终止。静态链表的「地址」就是下标,查找返回的是下标而非指针。
排除法:B 答 的人把查找值当成了答案下标。C 答 的人跟链只走了三站漏了最后一跳。A 答下标 的人以为按物理顺序找 ——物理位置与链上位置无关。
用静态链表从头建链存放读入序列 (尾插,cnt 从 起分配下标,nxt[t] = -1 接尾)。建完后 nxt[1] 与 nxt[2] 的值分别是( )。
考点:静态建链(L6)。
(L6)考点:静态建链——尾插 10 20 30,nxt[1]=2、nxt[2]=-1。
解析:本题考查静态建链。按到达顺序分配下标: 得 、 得 、 得 ;尾插让前一个的 nxt 指向后一个,末节点 nxt 为 。所以 nxt[1] = 2( 的后继是 )、nxt[2] = -1( 是尾)。
排除法:C 答 与 的人把两格的值互换;D 答 与 的人没理解「指后继」写成自指;B 答 与 的人既自指又越界。
01// 链表为 1 -> 2 -> 3 -> 4 -> 2(4 的 next 指回 2) 02Node* slow = head; 03Node* fast = head; 04int steps = 0; 05while (slow != fast || steps == 0) { 06 slow = slow->next; 07 fast = fast->next->next; 08 steps++; 09 if (slow == fast) break; 10} 11cout << slow->data;
输出是( )。
考点:判环执行(M1)。
(M1)考点:判环执行——快慢指针三追两赶在值 4 处相遇。
解析:本题考查判环执行。慢指针依次走到 ;快指针两步两跳依次到 :第三轮两者同在节点 ,slow == fast 成立,输出 。steps == 0 的附加条件只是为了让起点处 slow == fast 不被误判为「已相遇」。
排除法:B 答 的人把入环点当成了相遇点;A 答 的人以为起点即终点——起点同时出发但循环至少跑一轮;D 答 的人追踪快指针路线时跳错一步。
01// 链表为 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL 02Node* slow = head; 03Node* fast = head; 04while (fast != NULL && fast->next != NULL) { 05 slow = slow->next; 06 fast = fast->next->next; 07} 08cout << slow->data;
输出是( )。
考点:找中间执行(M2)。
(M2)考点:找中间执行——6 节点快慢指针停在后半第一个,值 4。
解析:本题考查找中间执行。慢:;快: 后再跳将越过 NULL 触发条件停止——快指针停在 处时(第三轮后 fast=6?逐轮核:初 ;一轮慢 快 ;二轮慢 快 ;三轮慢 快 ;四轮条件 fast->next 为 NULL 不再进。慢停 )。偶数长度取到后半第一个,是此写法的既定行为。
排除法:D 答 的人以为偶数链取前中;A 答 的人多数走了一步;C 答 的人把快指针的终点当成了慢指针的落点。
01// 链表为 1 -> 2 -> 3 -> 4 -> 5 -> NULL,删除倒数第 2 个节点 02int k = 2; 03Node* f = head; 04Node* s = head; 05for (int i = 0; i < k; i++) f = f->next; 06while (f->next != NULL) { 07 f = f->next; 08 s = s->next; 09} 10Node* t = s->next; 11s->next = t->next; 12delete t; 13// 从 head 输出整条链
输出是( )。
考点:删倒数第 k(M3)。
(M3)考点:删倒数第 k——快指针先行 2 步,同速推进后慢指针停在待删前驱处。
解析:本题考查删倒数第 k。f 先行 步到节点 ;随后 while (f->next) 推进直到 f 停在尾节点 ,此时 s 停在 (倒数第 个,即待删节点 的前驱);跨接删除后链变 1 2 3 5。用 f->next 而非 f 判停是为了让 s 恰好落在前驱上。
排除法:D 答 1 2 4 5 的人删的是倒数第 个;A 答 2 3 4 5 的人删了头节点;C 答 1 2 3 4 5 的人以为没改动——跨接真实发生了。
01// 链表为 1 -> 2 -> 3 -> 4 -> 5 -> NULL 02for (Node* p = head, *q = head->next; q != NULL; p = p->next, q = q->next) 03 cout << q->data << " ";
输出是( )。
考点:错位同速输出(M4)。
(M4)考点:错位同速输出——q 从第二节点起同速前进,输出 2 3 4 5。
解析:本题考查错位同速输出。q = head->next 起点领先一格,每轮 p、q 同步前移,循环以 q != NULL 收尾——输出的是从第二个到末尾的全部节点 2 3 4 5。这是「跳过表头」的最简写法。
排除法:A 答 1 2 3 4 的人以为输出的是 p 的序列;B 答 2 4 6 8 的人把「错位」误解成「翻倍跳步」;C 答 1 3 5 的人按奇数位跳节点,q 每轮只走一步。
快指针循环条件 while (fast != NULL && fast->next != NULL) 中,若删去第二个条件只留 fast != NULL,快指针走到两步时可能发生( )。
考点:快指针循环条件(M5)。
(M5)考点:快指针循环条件——删去 fast->next 条件,奇数链尾节点处解引用 NULL 崩溃。
解析:本题考查快指针循环条件。奇数长度链的最后一轮,快指针恰好停在尾节点(非 NULL),fast != NULL 放行;接着 fast->next->next 里第一个 ->next 得到 NULL,第二个就是解引用 NULL——崩溃。两个条件分别守住两步跳跃,一个都不能少(G5)。
排除法:B 答没有影响的人没推过奇数链收尾那一轮;A 答自动停止的人给指针虚构了刹车;D 答慢指针加速的人把条件的保护对象张冠李戴。
补全头插法插入新节点的关键语句:
01struct Node { int data; Node* next; }; 02Node* head = NULL; 03Node* t = new Node; 04t->data = 5; 05t->next = /* 1 */; 06head = t;
空位 /* 1 */ 处应填( )。
考点:补头插(N1)。
(N1)考点:补头插——新节点先接旧头:t->next = head。
解析:本题考查补头插。头插第一步是让新节点抓住现有链:t->next = head;随后的 head = t 题面已给。若填 NULL,旧链整个丢失;若填 t,新节点自环。
排除法:C 填 NULL 的人切断了新节点与旧链的联系,插一个丢一串;A 填 t 的人造成自引用循环;D 填 head->next 的人把新节点接到了第二个位置,那是「插到头节点之后」不是头插。
带尾指针建链,补全尾插的关键语句:
01Node* tail = NULL; 02Node* t = new Node; 03t->data = 5; 04t->next = NULL; 05if (head == NULL) head = t; 06else /* 1 */ ; 07tail = t;
空位 /* 1 */ 处应填( )。
考点:补尾插(N2)。
(N2)考点:补尾插——非空表尾插的关键句是 tail->next = t。
解析:本题考查补尾插。tail 恒指末节点,把它指向新节点即完成接入;题面随后的 tail = t 再挪尾指针。若误填 tail = t,旧尾与新节点根本没接上。
排除法:A 填 tail = t 的人跳过了「接线」直接挪指针,新节点悬空;B 填 t->next = tail 的人把方向接反;D 填 head = t 的人把尾插做成了头插还丢了链。
补全链表遍历的循环推进语句:
01int cnt = 0; 02for (Node* p = head; p != NULL; /* 1 */) 03 cnt++;
空位 /* 1 */ 处应填( )。
考点:补遍历(N3)。
(N3)考点:补遍历——循环推进语句是 p = p->next。
解析:本题考查补遍历。链表遍历的步进只有一种:沿当前节点的 next 走。p++ 只对连续内存有意义,链表节点地址不连续,加一加到不相干的内存上。
排除法:B 填 p++ 的人把指针运算错用在链表上;C 填 p = head 的人让循环原地打转死循环;A 填 cnt++ 的人把推进语句与计数语句弄混,循环条件永真。
删除单链表中 p 的后继节点,补全跨接语句:
Node* t = p->next; /* 1 */ ; delete t;
空位 /* 1 */ 处应填( )。
考点:补删除(N4)。
(N4)考点:补删除——前驱跨接:p->next = t->next。
解析:本题考查补删除。t 是待删者,前驱 p 要直接指向 t 的后继。题面随后的 delete t 归还内存;跨接在前、释放在后的顺序不可倒。
排除法:B 填 t->next = p->next 的人把赋值方向写反,链结构错乱;C 填 p = t->next 的人只挪了局部变量没改链;D 填 t = p 的人连要删谁都弄丢了。
读入 个数头插建链,补全循环条件:
01Node* head = NULL; 02int n, x; 03cin >> n; 04for (/* 1 */) { 05 cin >> x; 06 Node* t = new Node; 07 t->data = x; 08 t->next = head; 09 head = t; 10}
空位 /* 1 */ 处应填( )。
考点:补建链循环(N5)。
(N5)考点:补建链循环——读 n 个数循环 n 次:int i = 0; i < n; i++。
解析:本题考查补建链循环。for (int i = 0; i < n; i++) 标准计数循环跑 次,每次读一个数头插。若从 起且条件 i < n,只读 个数,少建一个节点。
排除法:A 填 int i = 1; i < n; i++ 的人只循环 次,丢最后一个数;C 填 head != NULL 的人把循环条件建立在一个尚未成形的指针上,空表起步即停;D 填无限循环的人没有终止条件,程序停不下来。
补全整链释放循环的关键语句:
01while (head != NULL) { 02 /* 1 */ ; 03 head = head->next; 04 delete t; 05}
空位 /* 1 */ 处应填( )。
考点:补释放(N6)。
(N6)考点:补释放——删除前先记下当前节点:Node* t = head。
解析:本题考查补释放。题面随后的 head = head->next; delete t; 说明 t 承担「记住当前节点」的角色:先记、再挪头、后释放。若直接 delete head 再挪,就是释放后使用。
排除法:B 填 delete head 的人先删后取,head->next 读的是已归还的内存;D 填 head = NULL 的人把整链入口掐断,后面全泄漏;A 填 Node* t = head->next 的人记错了对象——要删的是当前节点不是后继。
链表队列(头指针管出队、尾指针管入队)依次执行:入队 、入队 、出队、入队 。从队首到队尾输出,结果是( )。
考点:链表队列执行(O1)。
(O1)考点:链表队列执行——入 1 入 2 出 1 入 3,队列为 2 3。
解析:本题考查链表队列执行。队列先进先出:入队 后队列为 1 2;出队弹掉队首 ;入队 接尾。从队首到队尾输出 2 3。头指针(出)尾指针(入)各司其职(F2)。
排除法:B 答 1 2 3 的人忘了中间出过一次队;A 答 1 2 的人以为出队的是 或没执行出队;D 答 3 2 的人把输出方向弄反。
链表栈(头部即栈顶)依次执行:入栈 、入栈 、出栈。此时栈顶的值是( )。
考点:链表栈执行(O2)。
(O2)考点:链表栈执行——push 1 push 2 pop 后栈顶为 1。
解析:本题考查链表栈执行。头部即栈顶(F1):push 栈为 1;push 头插栈为 2,1;pop 删头弹掉 ,栈顶回到 。后进先出一步可验。
排除法:D 答 的人以为 pop 弹的是底;B 答 的人给不存在的元素安了值;C 答栈为空的人没数清还剩一个元素。
有序链表 1 -> 4 -> 7,先用有序插入法插入 ,再插入 ,从 head 输出的结果是( )。
考点:有序插入两次(O3)。
(O3)考点:有序插入两次——1 4 7 依次插 3 插 5,得 1 3 4 5 7。
解析:本题考查有序插入两次。插 :定位到 之前,链变 1 3 4 7;再插 :定位到 与 之间,链变 1 3 4 5 7。每次插入都基于当前有序状态重新定位,链始终保持整体有序(F5)。
排除法:C 答 1 3 5 4 7 的人第二次插到了 前;A 答 1 4 3 5 7 的人第一次就插错了位置;B 答 3 5 1 4 7 的人两次都做了头插。
约瑟夫问题: 个人编号 到 围成圈,从 号起报数,报到 出列,直到只剩一人。最后留下的是( )。
考点:约瑟夫幸存者(O4)。
(O4)考点:约瑟夫幸存者——5 人报 3 依次出列 3 1 5 2,最后留下 4 号。
解析:本题考查约瑟夫幸存者。接 K6 的出列序 ,最后未出列的是 号。模拟法小数据手推即可;大数据的递推公式(约瑟夫环公式)属课外拓展,初赛阶段掌握模拟就够。
排除法:D 答 号的人没推完整个出列过程;B 答 号的人漏了倒数第二轮;C 答 号的人在中途就停了手。
图的邻接表存储中,每个顶点挂一条「边链表」,这里对链表的使用方式属于( )。
考点:邻接表关联(O5)。
(O5)考点:邻接表关联——图的邻接表用链表按需挂边,适配稀疏图。
解析:本题考查邻接表关联。每个顶点挂自己的邻边链:度数悬殊的点各挂长短合适的链,加一条边就是头插一个节点。稀疏图下它远比邻接矩阵省空间。图的系统知识在专题 06 已复习,此处只看链表角色。
排除法:D 选变树的人混淆了图与树;A 选保证无环的人把存储方式与图性质混为一谈——环照样可以存;C 选一步可达的人把「存得下」误当「直达快」,遍历仍要沿链走。
在节点 p 之后插入新节点 t 时,若先执行 p->next = t; 再执行 t->next = p->next;,结果是( )。
考点:断链指针顺序(P1)。
(P1)考点:断链指针顺序——先改 p->next 会让 t->next 拿到 t 自己,后继丢失。
解析:本题考查断链指针顺序。正确顺序先 t->next = p->next(趁原后继还在);若先 p->next = t,再执行 t->next = p->next 时 p->next 已经是 t 自己——新节点自环,原后继及身后整段失联。顺序错了不报错,链悄悄断掉,是链表最阴险的坑。
排除法:C 选顺序无关的人没推过这两句的依赖关系——第二句读的正是第一句写的变量;D 选编译错误的人把逻辑错误当成语法检查;A 选自动修复的人给运行时虚构了纠错能力。
头插法只写了 t->next = head; 却忘了 head = t;,后果是( )。
考点:忘更新头指针(P2)。
(P2)考点:忘更新头指针——只接不改 head,插入等于没发生且内存泄漏。
解析:本题考查忘更新头指针。t->next = head 后新节点确实指向了旧链,但入口 head 依旧认旧首节点——没有任何指针通向 t,它成了孤儿节点;程序结束前没人 delete 它,内存白白流失。
排除法:B 选一切正常的人没意识到入口才是链表的「官方认证」;C 选链表变空的人把 head 指向想成了清空;D 选编译报错的人又把逻辑漏洞当成了语法错误。
输出链表每个节点时把循环条件写成 for (Node* p = head; p->next != NULL; p = p->next),直接后果是( )。
考点:遍历条件写错(P3)。
(P3)考点:遍历条件写错——判 p->next 少输出尾节点,空表直接崩溃。
解析:本题考查遍历条件写错。输出每个节点应判 p != NULL;写成 p->next != NULL 后,指针停在尾节点时条件为假提前退场,末节点不被输出。更糟的是空表时 p 为 NULL,p->next 直接解引用空指针崩溃。
排除法:A 选多输出一个节点的人方向说反——这是少输出;D 选没有影响的人没对比例数;C 选反向遍历的人把条件的作用域完全理解错。
delete p; 之后又执行 cout << p->data;,这属于( )。
考点:释放后使用(P4)。
(P4)考点:释放后使用——delete 后的指针是悬垂指针,再读是未定义行为。
解析:本题考查释放后使用。delete p 归还内存但不清除指针变量,p 里还留着旧地址(悬垂指针);再 p->data 读的是已不属于自己的内存,内容可能碰巧是旧值、可能是垃圾、可能触发崩溃。好习惯:释放后立即 p = NULL。
排除法:A 选安全无虞的人赌「碰巧没变」,未定义行为赌不得;D 选编译错误的人高估了编译器——运行期才会出事;B 选自动重分配的人给内存管理虚构了不存在的能力。
链表为空(head == NULL)时直接执行 head->data,结果是( )。
考点:空链表解引用(P5)。
(P5)考点:空链表解引用——空表直接 head->data 即对 NULL 解引用,运行崩溃。
解析:本题考查空链表解引用。head == NULL 时没有节点存在,-> 无处可去,典型表现是段错误崩溃。防御写法:访问前先 if (head != NULL),或函数入口统一判空(I8)。
排除法:D 选输出 的人以为空表有默认数据域——节点都没有,值无从谈起;C 选输出空格的人同样虚构了行为;B 选编译错误的人把运行期崩溃当成了编译期检查。
双向链表插入节点时只改了 p->next = t; t->prev = p;,漏改了 t->next->prev,后果是( )。
考点:双向漏改一边(P6)。
(P6)考点:双向漏改一边——漏改后继的 prev,反向遍历跳过新节点。
解析:本题考查双向漏改一边。只改 p->next = t; t->prev = p; 的半边:正向走 1 -> 2 -> 3 正确;反向走时 的 prev 仍指 , 被跳过——链的前后两个方向不再互为镜像。双向操作的纪律:每个受影响节点的两根指针都要过一遍。
排除法:B 选只需改一半的人违背了双向链表的基本结构;A 选变循环链表的人给漏改安上了另一个错误形态;D 选编译器自动补的人把人肉纪律寄托给了不存在的智能。
补全「删除双向链表中间节点 p」的代码(p 的前驱后继均非空):
01struct Node { int val; Node* prev; Node* next; }; 02void delMid(Node* p) { 03 /* 1 */ 04 delete p; 05}
空位 /* 1 */ 处应填( )。
考点:双向删除补全(Q1)。
(Q1)考点:双向删除补全——前后邻居互指再释放自己。
解析:本题考查双向删除补全。删除中间结点 p 的两句铁律:前驱的 next 跨过 p 指向后继(p->prev->next = p->next),后继的 prev 跨过 p 指向前驱(p->next->prev = p->prev),两个方向同时缝合,最后 delete p。GESP 五级近年多次以补全形态考这两句。
排除法:B 选把 p->next 与 p->prev 写进赋值右侧两次的人让邻居指向了邻居自己的旧指针,链原地打结;C 选把 p->prev = p->next 的人只改了 p 自己的两根、邻居还指着 p,删除无效;D 选 p->prev->next = p->next->prev 的人把两个指针域的值交叉对调,量纲全错还顺手删了前驱。
双向链表中在结点 p 之后插入结点 s(p 有后继),下列四句的正确组合与书写顺序是( )。
考点:双向插入顺序辨析(Q2)。
(Q2)考点:双向插入顺序辨析——先接新结点两根,再让旧邻居认人。
解析:本题考查双向插入顺序辨析。在 p 之后插 s 的正确次序:先把 s 的 prev/next 接向 p 与原后继(此时 p->next 还没被改,指向原后继),然后原后继的 prev 认 s,最后才让 p->next = s。若先执行 p->next = s,后面再读 p->next 拿到的已是 s 自己,原后继失联。
排除法:B 选第二句 p->next = s 先执行的人让后续 s->next = p->next 变成自指;C 选第三序的人第一步就覆盖 p->next,原后继被跳过;D 选第四序的人末尾 p->next = s 之前 p->next->prev = s 用的还是旧值,看似可行,但与标准序等价性不稳、且 s 的两根接得最晚,属于侥幸写法,非 GESP 认定次序。
删除双向链表结点 p(前驱后继均非空),下列写法中错误的是( )。
考点:找错误的删除语句(Q3)。
(Q3)考点:找错误的删除语句——自指型错误写法辨析。
解析:本题考查找错误的删除语句。错误写法把 p->next->prev = p->next、p->prev->next = p->prev——后继的 prev 指向自己(后继)、前驱的 next 指向自己(前驱),等于让邻居各自指向自己,p 虽被绕开但链在两个方向都断了。正确写法是让邻居互指。B 是标准两句;C 与 D 虽绕(先改后继的 prev 再借它回写)但语义正确。
排除法:A 认为标准两句错误的人方向反了;C 认为 C 错误的人没追踪 p->next->prev 已指向前驱后 ->next 回写正确;D 认为 D 错误的人同样没逐步执行——这两句最终与标准两句等价。
用哑结点统一删除链表中所有值为 x 的节点,补全删除语句:
01Node* eraseAll(Node* head, int x) { 02 Node dummy(0); 03 dummy.next = head; 04 Node* cur = &dummy; 05 while (cur->next) { 06 if (cur->next->data == x) { 07 Node* del = cur->next; 08 /* 1 */ 09 delete del; 10 } else { 11 cur = cur->next; 12 } 13 } 14 return dummy.next; 15}
空位 /* 1 */ 处应填( )。
考点:哑结点删除补全(Q4)。
(Q4)考点:哑结点删除补全——cur 停在前驱、跨接 del。
解析:本题考查哑结点删除补全。哑结点方案里 cur 永远停在待删结点的前驱(哑结点让头节点也有了前驱):删除动作就是 cur->next = del->next 跨过 del;cur 本身不动(下一个待查的正是刚接上的结点)。GESP 五级多次考此空。
排除法:A 填 cur = cur->next 的人只是走过待删结点、没有摘链;B 填 del->next = cur->next 的人赋值方向反了,链被折断;C 填 cur->next = nullptr 的人直接掐断后半段。
补全 Floyd 快慢指针判环的移动语句(slow 一步、fast 两步):
01bool hasCycle(Node* head) { 02 Node* slow = head; 03 Node* fast = head; 04 while (fast != NULL && fast->next != NULL) { 05 /* 1 */ 06 if (slow == fast) return true; 07 } 08 return false; 09}
空位 /* 1 */ 处应填( )。
考点:快慢指针判环补全(Q5)。
(Q5)考点:快慢指针判环补全——slow 一步 fast 两步各自独立前进。
解析:本题考查快慢指针判环补全。Floyd 判环的两句:slow = slow->next(一步)、fast = fast->next->next(两步)——两者各自独立沿链前进,有环必相遇(G1 的原理在此落地为补全)。干扰项的陷阱是把 slow 的新位置写成依赖 fast 或让 fast 依赖 slow。
排除法:A 选 slow = fast->next 的人让两指针纠缠,环上可能永远错过;B 选 fast = slow->next->next 的人让 fast 跟着 slow 走,追赶关系破坏;D 选第四项的人两个指针都从 fast 出发,slow 不再独立。
下面的单链表反转代码有一处错误,应修改的是( )。
01Node* reverse(Node* head) { 02 Node* prev = NULL; 03 Node* current = head; 04 while (current != NULL) { 05 Node* nxt = current->next; 06 current->next = nxt; 07 prev = current; 08 current = nxt; 09 } 10 return prev; 11}
考点:单链表反转找错(Q6)。
(Q6)考点:单链表反转找错——current->next 应接 prev 不是 nxt。
解析:本题考查单链表反转找错。三指针反转的每轮:先存后继 nxt,然后 current->next = prev 把当前结点掉头指向前驱(错误代码写成了 = nxt,等于维持原方向,反转失败),再推进 prev 与 current。GESP 六级 2024-03 以完全相同的形态考过。
排除法:A 选改存后继那句的人 prev->next 在首轮对空指针解引用;B 选改循环条件的人少反转最后一个结点;D 选改初值 prev = head 的人让首结点指向自己成环。
补全双向链表 append(尾插)的非空分支:
01void append(int data) { 02 Node* newNode = new Node(data); 03 if (head == NULL) { 04 head = tail = newNode; 05 } else { 06 /* 1 */ 07 } 08 ++size; 09}
空位 /* 1 */ 处应填( )。
考点:双向尾插补全(Q7)。
(Q7)考点:双向尾插补全——先接两根再挪 tail。
解析:本题考查双向尾插补全。非空链尾插三句:tail->next = newNode(旧尾接新)、newNode->prev = tail(新结点回认旧尾)、tail = newNode(尾指针最后挪)。挪 tail 必须放最后——先挪则旧尾失联。
排除法:A 只写第一句的人新结点的 prev 悬空;C 只写后两句的人旧尾的 next 没接上新结点;D 选第三序的人先挪 tail 再用 tail 接线,接到了自己身上。
单向循环链表(head != NULL)在头节点之后插入新节点,补全:
01void insertAfterHead(Node* head, int x) { 02 Node* newNode = new Node; 03 newNode->val = x; 04 /* 1 */ 05}
空位 /* 1 */ 处应填( )。
考点:循环链表头后插入补全(Q8)。
(Q8)考点:循环链表头后插入补全——先抄 head->next 再覆盖。
解析:本题考查循环链表头后插入补全。两句次序:newNode->next = head->next(先把原第二结点存进新结点)、head->next = newNode(再让头认新)。次序颠倒则 head->next 已被覆盖、新结点自指。这是 GESP 五级 2026-06 原题形态。
排除法:A 选 newNode->next = head 的人把新结点接到了头前面,圈上多绕一步;B 选第三序的人第二步 newNode->next = head->next 读到的已是 newNode 自己,自指成环;C 选第四序的人改了 head 本身,入口丢失。
双向链表中在结点 p 之前插入结点 s(均非空),正确的四句是( )。
考点:结点前插辨析(Q9)。
(Q9)考点:结点前插辨析——p 之前插 s 的四句次序。
解析:本题考查结点前插辨析。前插的镜像铁律:s 的两根先接(s->next = p、s->prev = p->prev),随后 p 的原前驱认 s(p->prev->next = s),最后 p->prev = s——若先执行末句,p->prev 已是 s,第三句变成 s->next = s 自指。验算确认正向 。
排除法:A 选后插四句的人没看题——那是 p 之后的操作;B 选第三序的人第一步就覆盖 p->prev,第三句读到的是 s 自己;D 选第四序的人两根接对了但末句 p->prev = s 放最后之前 p->prev 已被第三句读过——等价正确,但语句排列在 p->prev->next = s 前执行 p->prev = s 的变体使其与标准序有差异,GESP 判定以先接两根、后改双邻居为准。
双向链表用 head 与 tail 两个指针维护,判断链表为空,下列写法中不能正确工作的是( )。
考点:判空写法辨析(Q10)。
(Q10)考点:判空写法辨析——head 是指针要用 -> 不是点号。
解析:本题考查判空写法辨析。head 是指针:判空比地址 head == NULL 即可(tail == NULL、size == 0 同样正确——三个状态量同步维护时都可用)。head.data 用了点号访问指针成员,C++ 里编译不通过(GESP 五级 2025-06 原题陷阱),是「不能正确工作」的那一个。
排除法:B 以为 tail == NULL 不行的人没想通首尾同生同灭;C 以为 size == 0 不行的人低估了维护计数器的惯例;D 选 head.data 之外项的人把语法错误看漏了。
已知指向待删结点本身的指针,单链表与双向链表删除该结点的时间复杂度分别是( )。
考点:单双链表删除复杂度对比(Q11)。
(Q11)考点:单双链表删除复杂度对比——有 prev 指针删除 O(1)。
解析:本题考查单双链表删除复杂度对比。删除要改前驱的 next:双向链表的前驱就在 p->prev 里,两步缝合 ;单链表只有 next,得从头走一遍找前驱,。这就是双向链表「多一根指针换删除高效」的兑现(D6)。
排除法:A 答都 的人以为单链表也能一步定位前驱;B 答单 双 的人正好说反;C 答都 的人低估了 prev 的作用。
单向循环链表从 head 出发输出一圈,补全循环:
Node* p = head;
/* 1 {
cout << p->data << " ";
p = p->next;
} /* 2 */
空位 /* 1 与 /* 2 处应分别填( )。
考点:循环链表遍历补全(Q12)。
(Q12)考点:循环链表遍历补全——do-while 保证起点被访问。
解析:本题考查循环链表遍历补全。循环链表没有 NULL 终点:do { 访问; 前进 } while (p != head)——先访问后判断,起点恰访问一次、绕一圈回到 head 停止。用 while 判 NULL 会因圈上永远无 NULL 而死循环(E2/E6 的补全形态)。
排除法:A 选判 NULL 的人死循环;C 选判 p->next != NULL 的人同样死循环且漏访问起点判定;D 选判 p->next != head 的人提前一站停,末结点访问后 p->next 恰为 head 就退出——起点已访问但起点后直接停,少访问末结点之后回到起点的闭环验证,且首轮若单结点自环则少一次输出。
要删除单链表中结点 p(非尾结点)但拿不到头指针,可行的做法是( )。
考点:拷贝后继删除(R1)。
(R1)考点:拷贝后继删除——不知头指针时用「值覆盖」实现 O(1) 删除非尾结点。
解析:本题考查拷贝后继删除。本题考查拷贝后继删除。拿到 p 却拿不到前驱时:把后继的值拷进 p,再跨接删掉后继——逻辑上等价于删掉了 p(值已是后继的,位置让出来的是后继的)。验算:链 对 p(值1) 执行后变 。局限:不能删尾结点(尾没有后继可拷)。
排除法:B 选必须从头找前驱的人放弃了值覆盖技巧—— 换 的机会丢了;C 选直接 delete 的人让链在后继处断裂;D 选置空后删的人同样断链且白丢后继。
「二分查找只适用于数组,不适合链表」的根本原因是( )。
考点:二分不适用链表(R2)。
(R2)考点:二分不适用链表——跳跃式访问中点在链表上是 O(n)。
解析:本题考查二分不适用链表。本题考查二分不适用链表。二分每步要取中点:数组下标直达 ,链表只能从头走——单步 使整体退化为 还不如顺序扫。链表能排序(链上排序算法存在),只是不能高效随机访问。
排除法:A 选不能排序的人混淆了「排序」与「随机访问」——链表排序可行但慢;B 选不能比较的人没道理——比较与存储结构无关;D 选内存太大的人方向全错。
操作系统把 CPU 时间片轮流分给一组进程,一个进程时间片用完就切到下一个,轮完一圈回到开头。最适合建模这一轮转场景的结构是( )。
考点:循环链表进程调度(R3)。
(R3)考点:循环链表进程调度——时间片轮转天然是转圈结构。
解析:本题考查循环链表进程调度。本题考查循环链表进程调度。尾指头的循环链表从任一进程出发走一圈即完成一轮调度,回到起点无缝开始下一轮——操作系统时间片轮转调度的经典建模。单向链表走完即止表达不了「轮回」。
排除法:A 选单向链表的人丢掉了回到开头的语义;B 选二叉树的人引入了不存在的层次结构;C 选栈的人把先进先出的轮转变成了后进先出。