GESP 2024年12月认证 C++ 5级真题
一、单选题(每题2分,共30分)
👉 点击查看答案
题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
答案 | C | C | A | D | C | D | B | A | A | B | C | B | A | D | B |
第1题
下面关于链表和数组的描述,错误的是()。
• A. 当数据数量不确定时,为了应对各种可能的情况,需要申请一个较大的数组,可能浪费空间;此时用链表比较合适,大小可动态调整。
• B. 在链表中访问节点的效率较低,时间复杂度为O(n)。
• C. 链表插入和删除元素效率较低,时间复杂度为O(n)。
• D. 链表的节点在内存中是分散存储的,通过指针连在一起。
第2题
在循环单链表中,节点的next
指针指向下一个节点,最后一个节点的next
指针指向()。
• A. 当前节点
• B. nullptr
• C. 第一个节点
• D. 上一个节点
第3题
为了方便链表的增删操作,一些算法生成一个虚拟头节点,方便统一删除头节点和其他节点。下面代码实现了删除链表中值为val
的节点,横线上应填的最佳代码是()。
1 struct LinkedNode {
2 int val;
3 LinkedNode* next;
4 LinkedNode(int val) : val(val), next(nullptr) {}
5 };
6
7 void removeElements(LinkedNode* head, int val) {
8 if (head == nullptr) {
9 return;
10 }
11 LinkedNode* cur;
12 LinkedNode* dummyHead = new LinkedNode(0); // 虚拟头节点
13 // 在此处填入代码
14
15 while (cur->next != nullptr) {
16 if (cur->next->val == val) {
17 LinkedNode* tmp = cur->next;
18 cur->next = cur->next->next;
19 delete tmp;
20 tmp = nullptr;
21 } else {
22 cur = cur->next;
23 }
24 }
25 head = dummyHead->next;
26 delete dummyHead;
27 dummyHead = nullptr;
28 }
• A. dummyHead->next = head; cur = dummyHead;
• B. dummyHead->next = head->next; cur = dummyHead;
• C. dummyHead->next = head; cur = dummyHead->next;
• D. dummyHead->next = head->next; cur = dummyHead->next;
第4题
对下面两个函数,说法错误的是()。
1 int fibA(int n) {
2 if (n <= 1)
3 return n;
4 int f1 = 0, f2 = 1;
5 for (int i = 2; i <= n; ++i) {
6 int temp = f2;
7 f2 = f1 + f2;
8 f1 = temp;
9 }
10 return f2;
11 }
12
13 int fibB(int n) {
14 if (n <= 1)
15 return n;
16 return fibB(n - 1) + fibB(n - 2);
17 }
• A. 两个函数实现的功能相同。
• B. fibA
采用递推方式。
• C. fibB
采用的是递归方式。
• D. fibA
时间复杂度为O(n),fibB
的时间复杂度为O(n²)。
第5题
两块长方形土地的长宽分别为24和36米,要将它们分成正方形的小块,使得正方形的尺寸尽可能大。小杨采用如下的辗转相除函数gcd(24, 36)
来求正方形分块的边长,则函数gcd
调用顺序为()。
1 int gcd(int a, int b) {
2 int big = a > b ? a : b;
3 int small = a < b ? a : b;
4 if (big % small == 0) {
5 return small;
6 }
7 return gcd(small, big % small);
8 }
• A. gcd(24, 36)
、gcd(24, 12)
、gcd(12, 0)
• B. gcd(24, 36)
、gcd(12, 24)
、gcd(0, 12)
• C. gcd(24, 36)
、gcd(24, 12)
• D. gcd(24, 36)
、gcd(12, 24)
第6题
唯一分解定理表明,每个大于1的自然数可以唯一地写成若干个质数的乘积。下面函数将自然数n
的所有质因数找出来,横线上能填写的最佳代码是()。
void primeFactors(int n) {
while (n % 2 == 0) {
cout << 2 << " ";
n /= 2;
}
// 在此处填入代码
for (int i = 3; ... ; ... ) {
while (n % i == 0) {
cout << i << " ";
n /= i;
}
}
if (n > 2)
cout << n << " ";
}
• A. for (int i = 3; i <= n; i++)
• B. for (int i = 3; i * i <= n; i++)
• C. for (int i = 3; i <= n; i += 2)
• D. for (int i = 3; i * i <= n; i += 2)
第7题
下述代码实现素数表的埃拉托色尼(埃氏)筛法,筛选出所有小于等于n
的素数。
vector<bool> sieve_Eratosthenes(int n) {
vector<bool> isPrime(n + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= n; ++i) {
if (isPrime[i]) {
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
return isPrime;
}
下面说法,正确的是()。
• A. 代码的时间复杂度是 ( O(n\sqrt{n}) )。
• B. 在标记非素数时,代码从 ( i^2 ) 开始,可以减少重复标记。
• C. 代码会输出所有小于等于n
的奇数。
• D. 调用函数 sieve_Eratosthenes(10)
,函数返回值的数组中包含的元素有:2, 3, 5, 7, 9。
第8题
下述代码实现素数表的线性筛法,筛选出所有小于等于n
的素数。
vector<int> linearSieve(int n) {
vector<int> primes;
vector<bool> isComposite(n + 1, false);
for (int i = 2; i <= n; ++i) {
if (!isComposite[i]) {
primes.push_back(i);
}
for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) {
isComposite[i * primes[j]] = true;
if (i % primes[j] == 0) {
break;
}
}
}
return primes;
}
下面说法正确的是()。
• A. 线性筛的时间复杂度是O(n)。
• B. 每个合数会被其所有的质因子标记一次。
• C. 线性筛和埃拉托色尼筛的实现思路完全相同。
• D. 以上都不对。
第9题
考虑以下C++代码实现的快速排序算法:
int partition(vector<int>& arr, int left, int right) {
int pivot = arr[right]; // 基准值
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[right]);
return i + 1;
}
void quickSort(vector<int>& arr, int left, int right) {
if (left < right) {
int pi = partition(arr, left, right);
quickSort(arr, left, pi - 1);
quickSort(arr, pi + 1, right);
}
}
以下关于快速排序的说法,正确的是()。
• A. 快速排序通过递归对子问题进行求解。
• B. 快速排序的最坏时间复杂度是O(n log n)。
• C. 快速排序是一个稳定的排序算法。
• D. 在最优情况下,快速排序的时间复杂度是O(n)。
第10题
下面关于归并排序,描述正确的是()。
• A. 归并排序是一个不稳定的排序算法。
• B. 归并排序的时间复杂度在最优、最差和平均情况下都是 ( O(n \log n) )。
• C. 归并排序需要额外的O(1)空间。
• D. 对于输入数组{12,11,13,5,6,7}
,代码输出结果为:7 6 5 13 12 11
。
第11题
给定一个长度为n
的有序数组nums
,其中所有元素都是唯一的。下面的函数返回数组中元素target
的索引。
int binarySearch(vector<int>& nums, int target, int left, int right) {
if (left > right) {
return -1;
}
int middle = left + ((right - left) / 2);
if (nums[middle] == target) {
return middle;
} else if (nums[middle] < target) {
return binarySearch(nums, target, middle + 1, right);
} else {
return binarySearch(nums, target, left, middle - 1);
}
}
int Find(vector<int>& nums, int target) {
int n = nums.size();
return binarySearch(nums, target, 0, n - 1);
}
关于上述函数,描述不正确的是()。
• A. 函数采用二分查找,每次计算搜索当前搜索区间的中点,然后根据中点的元素值排除一半搜索区间。
• B. 函数采用递归求解,每次问题的规模减小一半。
• C. 递归的终止条件是中间元素的值等于target
,若数组中不包含该元素,递归不会终止。
• D. 算法的复杂度为O(log n)。
第12题
给定一个长度为n
的有序数组nums
,其中可能包含重复元素。下面的函数返回数组中某个元素target
的左边界,若数组中不包含该元素,则返回-1。例如在数组nums = [5,7,7,8,8,10]
中查找target = 8
,函数返回8在数组中的左边界的索引为3。则横线上应填写的代码为()。
int getLeftBoundary(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int middle = left + ((right - left) / 2);
if (target <= nums[middle]) {
// 在此处填入代码
} else {
left = middle + 1;
}
}
return nums[left] == target ? left : -1;
}
• A. right = middle - 1;
• B. right = middle;
• C. right = middle + 1;
• D. 以上都不对。
第13题
假设有多个孩子,数组g
保存所有孩子的胃口值。有多块饼干,数组s
保存所有饼干的尺寸。小杨给孩子们发饼干,每个孩子最多只能给一块饼干。饼干的尺寸大于等于孩子的胃口时,孩子才能得到满足。小杨的目标是尽可能满足越多数量的孩子,因此打算采用贪心算法来找出能满足的孩子的数目,则横线上应填写的代码为()。
int cookieForChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int index = s.size() - 1; // 饼干数组下标
int result = 0;
for (int i = g.size() - 1; i >= 0; i--) {
if (index >= 0 && s[index] >= g[i]) {
// 在此处填入代码
}
}
return result;
}
• A. result++; index--;
• B. result--; index--;
• C. result--; index++;
• D. result++; index++;
第14题
关于分治算法,以下说法中不正确的是()。
• A. 分治算法将问题分成子问题,然后分别解决子问题,最后合并结果。
• B. 归并排序采用了分治思想。
• C. 快速排序采用了分治思想。
• D. 冒泡排序采用了分治思想。
第15题
小杨编写了一个如下的高精度减法函数:
vector<int> highPrecisionSubtract(vector<int> a, vector<int> b) {
vector<int> result;
int borrow = 0;
for (int i = 0; i < a.size(); ++i) {
int digitA = a[i];
int digitB = i < b.size() ? b[i] : 0;
int diff = digitA - digitB - borrow;
if (diff < 0) {
diff += 10;
borrow = 1;
} else {
borrow = 0;
}
result.push_back(diff);
}
// 去除前导零
while (result.size() > 1 && result.back() == 0) {
result.pop_back();
}
return result;
}
下面说法,正确的是()。
• A. 如果数组a
表示的整数小于b
表示的整数,代码会正确返回二者的差为负数。
• B. 代码假设输入数字是以倒序存储的,例如500存储为{0, 0, 5}
。
• C. 代码的时间复杂度为O(a.size() + b.size())。
• D. 当减法结果为0时,结果数组仍然会存储很多个元素0。
二、判断题(每题2分,共20分)
-
单链表只支持在表头进行插入和删除操作。
• □ 正确
• □ 错误
-
线性筛相对于埃拉托斯特尼筛法,每个合数只会被它的最小质因数筛去一次,因此效率更高。
• □ 正确
• □ 错误 -
任何一个大于1的自然数都可以分解成若干个不同的质数的乘积,且分解方式是唯一的。
• □ 正确
• □ 错误 -
贪心算法通过每一步选择当前最优解,从而一定能获得全局最优解。
• □ 正确
• □ 错误 -
递归算法必须有一个明确的结束条件,否则会导致无限递归并可能引发栈溢出。
• □ 正确
• □ 错误 -
快速排序和归并排序的平均时间复杂度均为 ( O(n \log n) ),且都是稳定排序。
• □ 正确
• □ 错误 -
快速排序的时间复杂度总比插入排序的时间复杂度低。
• □ 正确
• □ 错误 -
二分查找仅适用于数组而不适合链表,因为二分查找需要跳跃式访问元素,链表中执行跳跃式访问的效率低。
• □ 正确
• □ 错误 -
对有序数组
{5,13,19,21,37,56,64,75,88,92,100}
进行二分查找,成功查找元素19的比较次数是2。
• □ 正确
• □ 错误 -
递归函数每次调用自身时,系统都会为新开启的函数分配内存,以存储局部变量、调用地址和其他信息等,导致递归通常比迭代更加耗费内存空间。
◦ □ 正确
◦ □ 错误
3 编程题(每题25分,共50分)
3.1 编程题1:奇妙数字
试题名称:奇妙数字
时间限制:1.0 s
内存限制:512.0 MB
题面描述
小杨认为一个数字 ( x ) 是奇妙数字当且仅当 ( x = p^a ),其中 ( p ) 为任意数且 ( a ) 为正整数。例如,8 = 2(^3),所以8是奇妙数字,而6不是。
对于一个正整数 ( n ),小杨想要构建一个包含 ( m ) 个奇妙数字的集合 ({x_1, x_2, \ldots, x_m}),使其满足以下条件:
- 集合中不包含相同的数字。
- ( x_1 \times x_2 \times \ldots \times x_m ) 是 ( n ) 的因子。
小杨希望集合包含的奇妙数字尽可能多,请你帮他计算出满足条件的集合最多包含多少个奇妙数字。
输入格式
第一行包含一个正整数 ( n )。
输出格式
输出一个正整数,代表满足条件的集合最多包含的奇妙数字个数。
样例
输入:
128
输出:
3
样例解释
符合题意的一个包含3个奇妙数字的集合是 {2, 4, 8}
。其乘积为64,是128的因子。
参考程序
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+10;
ll calc(ll x) {
int ans = 0;
ll tmp = 1;
while (x >= tmp) {
ans++;
x -= tmp;
tmp++;
}
return ans;
}
int main() {
ll n;
cin >> n;
ll ans = 0;
for (ll i = 2; i * i <= n; i++) {
if (n % i == 0) {
int cnt = 0;
while (n % i == 0) {
cnt++;
n /= i;
}
ans += calc(cnt);
}
}
if (n != 1) {
ans += calc(1);
}
cout << ans << "\n";
}
3.2 编程题2:武器强化
试题名称:武器强化
时间限制:1.0 s
内存限制:512.0 MB
题面描述
小杨有 ( n ) 种武器和 ( m ) 种强化材料。第 ( i ) 种强化材料会适配第 ( p_i ) 种武器,小杨可以花费 ( c_i ) 金币将该材料对应的适配武器修改为任意武器。
小杨最喜欢第1种武器,因此他希望适配该武器的强化材料种类数严格大于其他武器。请计算满足该条件最少需要花费多少金币。
输入格式
第一行包含两个正整数 ( n, m )。
之后 ( m ) 行,每行包含两个正整数 ( p_i, c_i ),表示第 ( i ) 种材料的适配武器和修改花费。
输出格式
输出一个整数,表示最少需要的金币。
样例
输入:
3 3
1 5
2 3
3 1
输出:
1
样例解释
将第三种材料的适配武器从3改为1后,武器1有2种适配材料,武器2和3各有1种,满足条件。
参考程序
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n, m;
int cnt[1010];
vector<int> cs[1010];
ll calc(int aim) {
int cur_cnt = cnt[1];
ll res = 0;
vector<int> tmp;
for (int i = 2; i <= n; i++) {
int buy = max((int)cs[i].size() - aim + 1, 0);
for (int j = 0; j < buy; j++) {
res += cs[i][j];
}
cur_cnt += buy;
for (int j = buy; j < cs[i].size(); j++) {
tmp.push_back(cs[i][j]);
}
}
sort(tmp.begin(), tmp.end());
for (int i = 0; i < aim - cur_cnt; i++) {
res += tmp[i];
}
return res;
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int p, c;
cin >> p >> c;
cnt[p]++;
cs[p].push_back(c);
}
for (int i = 1; i <= n; i++) {
sort(cs[i].begin(), cs[i].end());
}
ll ans = 1e18;
for (int i = max(cnt[1], 1); i <= m; i++) {
ans = min(ans, calc(i));
}
cout << ans << "\n";
}