GESP 客观题评测系统

2026-06-Level-5

2026-06-Level-5

试卷解析总览,可直接查看每题答案与解析。

单选题

1 题(单选题2 分)

假设 head != nullptr,下面是实现单向循环链表在头节点后插入新节点的代码,横线处应填入()。

struct Node {
    int val;
    Node* next;
};

void insertAfterHead(Node* head, int x) {
    Node* newNode = new Node;
    newNode->val = x;
    // 在此处填入代码
}
A.
newNode->next = head;
head->next = newNode;
B.
newNode->next = head->next;
head->next = newNode;
C.
head->next = newNode;
newNode->next = head->next;
D.
newNode->next = head->next;
head = newNode;

正确答案B

解析详情

【答案】B

【考点】循环单链表插入

【解析】 插入前先用 newNode->next 保存 head->next,使新节点指向原来的后继节点;再令 head->next = newNode,把新节点接到 head 后。这样原有循环链不断开。

【易错点】 若先覆盖 head->next 再读取它,会丢失原后继节点或让新节点错误地自环。

2 题(单选题2 分)

下面代码遍历并输出一个循环单链表,其中 head 指向链表的第一个节点,横线处应填入的是()。

struct Node {
    int val;
    Node* next;
};

void printList(Node* head) {
    if (head == nullptr) return;
    Node* p = head;
    // 在此处填入代码
    cout << endl;
}
A.
while (p != nullptr) {
    cout << p->val << " ";
    p = p->next;
}
B.
while (p->next != nullptr) {
    cout << p->val << " ";
    p = p->next;
}
C.
do {
    cout << p->val << " ";
    p = p->next;
} while (p != head);
D.
for (; p; p = p->next) {
    cout << p->val << " ";
}

正确答案C

解析详情

【答案】C

【考点】循环单链表遍历

【解析】 p 从 head 开始,每轮输出当前节点后移动到 next;当 p 再次回到 head 时,说明所有节点恰好访问一遍。do-while 还能保证只有一个节点时也会输出一次。

【易错点】 循环链表末尾不指向 nullptr,使用 p != nullptr 会造成死循环。

3 题(单选题2 分)

双链表结点定义如下,若要删除双链表中的中间结点(非首尾节点)p,下面写法正确的是()。

struct Node {
    int val;
    Node* prev;
    Node* next;
};

A

p->prev->next = p->next;
p->next->prev = p->prev;
delete p;

B.

p->next->prev = p->next;
p->prev->next = p->prev;
delete p;
A.
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
B.
p->next->prev = p->next;
p->prev->next = p->prev;
delete p;
C.
p->prev = p->next;
p->next = p->prev;
delete p;
D.
p->next->next = p->prev;
p->prev->prev = p->next;
delete p;

正确答案A

解析详情

【答案】A

【考点】双链表删除

【解析】 删除中间节点 p 时,要让前驱的 next 指向 p 的后继,同时让后继的 prev 指向 p 的前驱。两侧重新连接后再 delete p,链表关系保持完整。

【易错点】 双链表删除必须同时修改前驱和后继两个方向的指针。

4 题(单选题2 分)

使用如下欧几里得算法求 gcd(105, 45) 时,函数 gcd(a, b) 的递归调用序列正确的是()。

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}
A.
gcd(105, 45) -> gcd(45, 60) -> gcd(60, 15) -> gcd(15, 0)
B.
gcd(105, 45) -> gcd(45, 15) -> gcd(15, 0)
C.
gcd(105, 45) -> gcd(60, 45) -> gcd(15, 45)
D.
gcd(105, 45) -> gcd(15, 45) -> gcd(15, 0)

正确答案B

解析详情

【答案】B

【考点】欧几里得算法

【解析】 105 % 45 = 15,所以第一次递归为 gcd(45, 15);45 % 15 = 0,继续得到 gcd(15, 0),此时返回 15。

【易错点】 递归参数是 (b, a % b),不要把余数误算成 a - b。

5 题(单选题2 分)

下面代码实现线性筛(欧拉筛),以筛选出 n 以内的所有素数。横线处的代码应为()。

vector<int> sieve(int n) {
    vector<bool> is_prime(n + 1, true);
    vector<int> primes;

if (n >= 0) is_prime[0] = false;
    if (n >= 1) is_prime[1] = false;

for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            primes.push_back(i);
        }
        for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) {
            is_prime[i * primes[j]] = false;
            if (___ break; // 在此处填入代码)
        }
    }
    return primes;
}
A.
i % primes[j] == 0
B.
primes[j] % i == 0
C.
i % primes[j] != 0
D.
i == primes[j]

正确答案A

解析详情

【答案】A

【考点】欧拉筛

【解析】 当 i % primes[j] == 0 时,primes[j] 已是 i 的最小质因子;继续枚举更大的质数会让后续合数被重复筛除。因此此处立即 break,保证每个合数只由其最小质因子筛一次。

【易错点】 break 的条件是 primes[j] 能整除 i,而不是 i 能否整除 primes[j]。

6 题(单选题2 分)

下面关于埃氏筛法的说法正确的是()。

A.
每个合数只会被筛掉一次
B.
从每个素数出发,把它的倍数标记为合数
C.
只能判断一个数是不是偶数
D.
不能求出素数表

正确答案B

解析详情

【答案】B

【考点】埃拉托斯特尼筛法

【解析】 埃氏筛从未被标记的素数 p 出发,将 p 的倍数标记为合数,最后未被标记的数就是素数。同一个合数可能含有多个质因子,因此可能被标记多次。

【易错点】 “每个合数只筛一次”是欧拉筛的特点,不是普通埃氏筛的保证。

7 题(单选题2 分)

下面代码实现了计算 x^{n} 的快速幂算法,该算法体现的编程思想是()。

long long power(long long x, int n) {
    if (n == 0) return 1;
    long long res = power(x, n / 2);
    if (n % 2 == 0) return res * res;
    else return res * res * x;
}
A.
枚举
B.
贪心
C.
分治
D.
模拟

正确答案C

解析详情

【答案】C

【考点】分治与快速幂

【解析】 函数先递归计算 x^(n/2),再通过平方得到偶数次幂;n 为奇数时额外乘一个 x。每次把指数规模减半,体现了分治思想。

【易错点】 递归不等同于分治,关键在于问题规模由 n 缩小为 n/2。

8 题(单选题2 分)

下面代码用于统计 n 中因子 2 出现了多少次。若 n = 40 ,输出是( )。

int n = 40;
int cnt = 0;
while (n % 2 == 0) {
    cnt++;
    n / = 2;
}
cout << cnt;
A.
1
B.
2
C.
3
D.
4

正确答案C

解析详情

【答案】C

【考点】质因数分解

【解析】 40 连续除以 2 的过程为 40→20→10→5,共成功整除 3 次,因此 cnt 最终为 3。

【易错点】 统计的是质因子 2 的指数,不是 40 的全部因子个数。

9 题(单选题2 分)

在一个有序数组中查找第一个大于或等于 x 的元素位置,横线处应填写()。

int lowerBound(vector<int>& a, int x) {
    int l = 0, r = a.size();
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (a[mid] >= x) _____; // 在此处填入代码
        else l = mid + 1;
    }
    return l;
}
A.
r = mid + 1
B.
r = mid - 1
C.
r = mid
D.
l = mid

正确答案C

解析详情

【答案】C

【考点】二分查找下界

【解析】 当 a[mid] >= x 时,mid 可能正是第一个满足条件的位置,不能将它排除,只能把右边界缩到 r = mid;否则令 l = mid + 1。循环结束时 l 即为下界。

【易错点】 这里使用左闭右开区间 [l, r),不能写成 r = mid - 1。

10 题(单选题2 分)

有若干根木头,长度存于 wood。每切一刀可以把一段木头分成两段。函数 check(wood, K, x) 返回:用不超过 K 刀,能否使所有木段长度都不超过 x。下面代码使用二分答案查找最小可行的 x,横线处应填()。

int binary_cut(vector<int>& wood, int K) {
    int l = 1;
    int r = 0;
    for (int len : wood) r = max(r, len);
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (check(wood, K, mid))
            ________________;
        else
            l = mid + 1;
    }
    return l;
}
A.
r = mid + 1
B.
r = mid
C.
l = mid
D.
r = mid - 1

正确答案B

解析详情

【答案】B

【考点】二分答案

【解析】 check(mid) 为真说明 mid 已可行,但还要继续寻找更小的可行值,所以保留 mid 并令 r = mid;不可行时才令 l = mid + 1。最终 l、r 收敛到最小可行的 x。

【易错点】 寻找最小可行值时,可行的 mid 不能被 r = mid - 1 直接排除。

11 题(单选题2 分)

下面代码段实现了快速排序的划分操作(以首元素为基准),横线处代码应填入()。

int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[low];
    int i = low, j = high;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--;
        while (i < j && arr[i] <= pivot) i++;
        if (i < j) swap(arr[i], arr[j]);
    }
    // 在此处填入代码
    return i;
}
A.
swap(arr[low], arr[high])
B.
swap(arr[low], arr[i])
C.
swap(arr[i], arr[high])
D.
arr[i] = pivot

正确答案B

解析详情

【答案】B

【考点】快速排序划分

【解析】 双指针相遇时,i 左侧元素不大于 pivot,i 右侧元素不小于 pivot。基准值仍在 arr[low],交换 arr[low] 与 arr[i] 后,pivot 才位于最终划分位置 i。

【易错点】 只写 arr[i] = pivot 会覆盖原值,不能完成基准元素与相遇位置的交换。

12 题(单选题2 分)

下面哪句话最符合归并排序的思想?()

A.
每次选择最小元素放到前面
B.
将数组分成两半分别排序,再合并两个有序部分
C.
相邻元素两两交换
D.
从左到右把元素插入有序区

正确答案B

解析详情

【答案】B

【考点】归并排序

【解析】 归并排序先递归地把数组分成两半并分别排好序,再用线性合并操作得到完整的有序数组,属于典型的分治过程。

【易错点】 “选择最小元素放前面”描述的是选择排序,不是归并排序。

13 题(单选题2 分)

在对长度为 n(n ≥ 1)的数组进行归并排序的过程中,mergeArray 函数(合并两个有序子数组的操作)被调用的次数是()。

const int MAXN = 100005;
int a[MAXN];
int tempArr[MAXN];

void mergeArray(int left, int mid, int right) {
    int i = left;
    int j = mid + 1;
    int k = left;
    while (i <= mid && j <= right) {
        if (a[i] <= a[j]) tempArr[k++] = a[i++];
        else tempArr[k++] = a[j++];
    }
    while (i <= mid) tempArr[k++] = a[i++];
    while (j <= right) tempArr[k++] = a[j++];
    for (int p = left; p <= right; p++) a[p] = tempArr[p];
}

void mergeSort(int left, int right) {
    if (left >= right) return;
    int mid = left + (right - left) / 2;
    mergeSort(left, mid);
    mergeSort(mid + 1, right);
    mergeArray(left, mid, right);
}
A.
n - 1
B.
log n
C.
n log n
D.
2n

正确答案A

解析详情

【答案】A

【考点】归并排序递归结构

【解析】 递归最终产生 n 个只含一个元素的叶子区间;每次 mergeArray 把两个子区间合成一个。把 n 个独立区间合并成 1 个区间恰好需要 n - 1 次合并。

【易错点】 O(n log n) 是总时间复杂度,不是 mergeArray 的调用次数。

14 题(单选题2 分)

小杨在学校义卖会上负责打包“零食盲盒”。每个盲盒重量不同,快递盒最多承重 limit 克,每个快递盒最多装两个盲盒。为了尽量少用快递盒,他每次尝试把最轻和最重的盲盒装在一起;若总重超过 limit,则只装最重的盲盒。下面代码横线处应填入的是()。

int minBoxes(vector<int>& w, int limit) {
    sort(w.begin(), w.end());
    int l = 0, r = w.size() - 1;
    int boxes = 0;
    while (l <= r) {
        if (w[l] + w[r] <= limit) {
            __________;
        } else {
            r--;
        }
        boxes++;
    }
    return boxes;
}
A.
l++;
B.
r--;
C.
l++;
r--;
D.
boxes--;

正确答案C

解析详情

【答案】C

【考点】贪心与双指针

【解析】 当最轻的 w[l] 与最重的 w[r] 之和不超过 limit 时,两者可共用一个盒子,因此要同时执行 l++ 和 r--。若二者都装不下,最重者只能单独装,故只移动 r。

【易错点】 配对成功后若只移动一个指针,会把已经装箱的盲盒重复计算。

15 题(单选题2 分)

高精度减法中,假设两个高精度数按低位在前存储,且已经保证被减数不小于减数。下面处理借位逻辑代码中横线处应填入()。

if (a[i] < b[i]) {
    a[i + 1]--;
    ________________;
}
t = a[i] - b[i];
A.
a[i] += 10
B.
a[i] -= 10
C.
b[i] += 10
D.
a[i] = a[i+1] + 10

正确答案A

解析详情

【答案】A

【考点】高精度减法借位

【解析】 当 a[i] < b[i] 时,从高一位 a[i+1] 借 1,并把当前十进制位增加 10,所以应执行 a[i] += 10;随后 t = a[i] - b[i] 得到当前位结果。

【易错点】 向高位借 1 后,当前位增加的是 10,而不是减少 10。

判断题

1 题(判断题2 分)

数组的存储空间在物理上通常是连续的,而链表的结点可以存储在不连续的内存空间中。

正确答案正确

解析详情

【答案】正确

【考点】数组与链表的存储结构

【解析】 数组元素按固定步长连续存放,才能通过下标直接计算地址;链表依靠指针连接节点,各节点在内存中不必连续。

【易错点】 逻辑上相邻的链表节点不代表物理地址也相邻。

2 题(判断题2 分)

带哨兵头尾节点的双向循环链表,在表头插入节点 p,以下四步操作无论什么顺序执行结果都正确。

 p->next = head->next;
 p->prev = head;
 head->next->prev = p;
 head->next = p;

正确答案错误

解析详情

【答案】错误

【考点】双向链表插入顺序

【解析】 步骤①和③必须在④之前读取原来的 head->next;若先执行④,head->next 已变成 p,后续可能令 p->next = p 或只修改 p 自身的 prev,破坏链接。因此四步不能任意排序。

【易错点】 修改指针前要先保存仍会被后续操作使用的旧指针值。

3 题(判断题2 分)

对任意正整数 a、b,以下两种写法的 gcd 函数返回值完全相同。

int gcd1(int a, int b) {
    return b ? gcd1(b, a % b) : a;
}

int gcd2(int a, int b) {
    while (b) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

正确答案正确

解析详情

【答案】正确

【考点】欧几里得算法的递归与迭代

【解析】 gcd1 和 gcd2 每轮都把 (a, b) 更新为 (b, a % b),并都在 b = 0 时返回 a;二者只是递归和循环两种实现,返回值相同。

【易错点】 比较两段代码时应核对状态转移和终止条件,而不能只看实现形式。

4 题(判断题2 分)

在归并排序的合并操作中,如下代码片段可以正确地将两个已排序的子数组 L 和 R 合并回原数组 arr 中。

void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    vector<int> L(n1), R(n2);

for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) arr[k++] = L[i++];
        else arr[k++] = R[j++];
    }
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

正确答案正确

解析详情

【答案】正确

【考点】归并排序的合并操作

【解析】 代码先复制左右两个有序区间,再用 i、j 比较当前最小元素并写入 arr[k];主循环结束后补写尚未耗尽的一侧,因此区间 [left, right] 会被完整地合并为有序序列。

【易错点】 主循环结束时通常仍有一侧存在剩余元素,两个收尾循环不能省略。

5 题(判断题2 分)

分治法通常将一个规模较大的问题拆分为若干个规模较小、结构相似的子问题,分别求解后再合并子问题的结果。

正确答案正确

解析详情

【答案】正确

【考点】分治法

【解析】 分治法的核心就是“分解—求解—合并”:把原问题拆成结构相似、规模更小的子问题,分别求解后组合成原问题的答案。

【易错点】 分治通常需要合并子问题结果,不只是把任务简单拆开。

6 题(判断题2 分)

贪心算法只要每一步选择当前最优解,就一定能得到全局最优解。

正确答案错误

解析详情

【答案】错误

【考点】贪心算法适用条件

【解析】 局部最优选择只有在问题满足贪心选择性质和最优子结构时,才能保证得到全局最优解;对一般问题逐步取当前最优可能错过整体最优方案。

【易错点】 贪心是一种有适用条件的策略,不是所有优化问题的通用保证。

7 题(判断题2 分)

二分查找不仅可以应用于有序数组,也可以在不增加时间复杂度的情况下应用于有序的单链表,因为链表也支持 O(1) 时间内的随机访问。

正确答案错误

解析详情

【答案】错误

【考点】链表访问复杂度

【解析】 单链表不支持 O(1) 随机访问,从表头找到中间节点需要 O(n) 时间;即使元素有序,也无法像数组那样在 O(log n) 时间内完成常规二分查找。

【易错点】 有序只是二分查找的条件之一,还需要高效访问中间位置。

8 题(判断题2 分)

以下函数 f1 的时间复杂度比函数 f2 的更高。

void f1(int n) {
    for (int i = 1; i < n; i *= 2);
}

void f2(int n) {
    if (n <= 1) return;
    f2(n - 1);
    f2(n - 1);
}

正确答案错误

解析详情

【答案】错误

【考点】循环与递归复杂度

【解析】 f1 中 i 每次乘 2,循环约 log₂n 次,复杂度为 O(log n);f2 每层产生两个规模为 n-1 的调用,满足 T(n)=2T(n-1)+O(1),复杂度为 O(2^n)。因此 f1 更低。

【易错点】 递归代码行数少不代表复杂度低,要计算递归调用树的规模。

9 题(判断题2 分)

唯一分解定理表明,任何一个大于1的自然数都可以唯一地分解为若干个质数的乘积,如果不考虑质因数的顺序,这种分解方式是唯一的。

正确答案正确

解析详情

【答案】正确

【考点】算术基本定理

【解析】 算术基本定理指出,每个大于 1 的自然数都能写成若干质数的乘积,并且在不计质因数排列顺序时,这种分解是唯一的。例如 12=2×2×3,交换三个质因数的书写次序不产生新的分解。

【易错点】 “忽略顺序”不等于忽略质因数的重复次数;12 的分解中两个 2 都必须保留。

10 题(判断题2 分)

归并排序和快速排序在平均情况下的时间复杂度均为 O(n \log n) 。但在稳定性方面,归并排序通常是不稳定的,而快速排序是稳定的。

正确答案错误

解析详情

【答案】错误

【考点】排序算法的复杂度与稳定性

【解析】 两种排序的平均时间复杂度通常都是 O(n log n),但稳定性结论写反了:归并排序通常稳定,而常见的原地快速排序通常不稳定。

【易错点】 不要把时间复杂度相同误认为稳定性也相同。