GESP 客观题评测系统

2025-06-Level-5

2025-06-Level-5

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

单选题(每题 2 分)

1 题(单选题

与数组相比,链表在()操作上通常具有更高的效率。

A.
随机访问元素
B.
查找指定元素
C.
在已知位置插入或删除节点
D.
遍历所有元素

正确答案C

解析详情

【答案】C

【考点】链表与数组的对比

【解析】 数组是连续存储,随机访问效率高(O(1)),但在中间插入或删除需要移动大量元素(O(n))。链表是非连续存储,在已知位置插入或删除节点只需修改指针指向,效率为 O(1)。

【易错点】 容易混淆“已知位置”和“按值查找”的效率,链表按值查找仍需遍历,效率为 O(n)。

2 题(单选题

下面C++代码实现双向链表。函数 is_empty() 判断链表是否为空,如链表为空返回 true,否则返回 false。横线处不能填写()。// 节点结构体

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

// 双向链表结构体

struct DoubleLink {
    Node* head;
    Node* tail;
    int size;

    DoubleLink() {
        head = nullptr;
        tail = nullptr;
        size = 0;
    }

    ~DoubleLink() {
        Node* curr = head;
        while (curr) {
            Node* next = curr->next;
            delete curr;
            curr = next;
        }
    }

    // 判断链表是否为空
    bool is_empty() const {
        ___
    }
};
A.
return head == nullptr;
B.
return tail == nullptr;
C.
return head.data == 0;
D.
return size == 0;

正确答案C

解析详情

【答案】C

【考点】双向链表基本操作

【解析】 链表为空的标志通常是头指针(head)或尾指针(tail)为 nullptr,或者计数器 size 为 0。选项 C 尝试访问 head 的成员 data,如果链表为空,head 是空指针,访问 head.data 会导致运行错误,且 data 的数值并不能代表链表是否为空。

【易错点】 忽视空指针访问风险,误以为 data 初始值为 0 即可代表为空。

3 题(单选题

基于上题代码正确的前提下,填入相应代码完善 append(),用于在双向链表尾部增加新节点,横线上应填写()。

void append(int data) {
    Node* newNode = new Node{data, nullptr, nullptr};
    if (is_empty()) {
        head = tail = newNode;
    } else {
        ___
    }
    ++size;
}
A.
tail->next = newNode;
B.
newNode->prev = tail;
tail = newNode;
C.
tail = newNode;
newNode->prev = tail;
tail->next = newNode;
D.
tail->next = newNode;
newNode->prev = tail;
tail = newNode;

正确答案D

解析详情

【答案】D

【考点】双向链表节点插入

【解析】 在双向链表尾部插入新节点需执行三步:1. 将原尾节点的 next 指向新节点(tail->next = newNode);2. 将新节点的 prev 指向原尾节点(newNode->prev = tail);3. 更新尾指针指向新节点(tail = newNode)。

【易错点】 指针更新顺序错误会导致链表断裂或自环,如先更新 tail 则无法找回原尾节点。

4 题(单选题

下列C++代码用循环链表解决约瑟夫问题,即假设 n 个人围成一圈,从第一个人开始数,每次数到第 k 个的人就出圈,输出最后留下的那个人的编号。横线上应填写()。

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

Node* createCircularList(int n) {
    Node* head = new Node{1, nullptr};
    Node* prev = head;
    for (int i = 2; i <= n; ++i) {
        Node* node = new Node{i, nullptr};
        prev->next = node;
        prev = node;
    }
    prev->next = head;
    return head;
}

int fingLastSurvival(int n, int k) {
    Node* head = createCircularList(n);
    Node* p = head;
    Node* prev = nullptr;

    while (p->next != p) {
        for (int count = 1; count < k; ++count) {
            prev = p;
            p = p->next;
        }
        ___
    }
    cout << "最后留下的人编号是:" << p->data << endl;
    delete p;
    return 0;
}
A.
prev->next = p->next;
delete p;
p = prev->next;
B.
delete p;
prev->next = p->next;
p = prev->next;
C.
delete p;
p = prev->next;
prev->next = p->next;
D.
prev->next = p->next;
p = prev->next;
delete p;

正确答案A

解析详情

【答案】A

【考点】约瑟夫环、单向循环链表节点删除

【解析】 当 p 移动到待出圈节点时,prev 指向其前驱。删除操作应先将前驱指向 p 的后继(prev->next = p->next),然后释放 p 的内存(delete p),最后将 p 移向下一个起始位置(p = prev->next)。

【易错点】 如果先 delete p,则无法安全访问 p->next 来更新链表。

5 题(单选题

下列C++代码判断一个正整数是否是质数,说法正确的是()。

bool is_prime(int n) {
    if (n <= 1)
        return false;
    if (n == 2 || n == 3 || n == 5)
        return true;
    if (n % 2 == 0 || n % 3 == 0 || n % 5 == 0)
        return false;

    int i = 7;
    int step = 4;
    int finish_number = sqrt(n) + 1;
    while (i <= finish_number) {
        if (n % i == 0)
            return false;
        i += step;
        step = 6 - step;
    }
    return true;
}
A.
代码存在错误,比如5是质数,但因为5%5余数是0返回了false
B.
finish_number 的值应该是 n / 2,当前写法将导致错误
C.
当前 while 循环正确的前提是:所有大于3的质数都符合6k±16k \pm 1形式
D.
while 循环修改如下,其执行效果和执行时间相同。for (int i = 2; i < finish_number; i++) { if (n % i == 0) return false; } return true;

正确答案C

解析详情

【答案】C

【考点】质数判定、6k±1 优化算法

【解析】 代码利用了除了 2, 3 外的所有质数都分布在 6 的倍数相邻两侧(即 6k-1 和 6k+1)的特性。循环从 7 开始,步长在 4 和 2 之间切换(7, 11, 13, 17...),跳过了所有 2, 3, 5 的倍数,大幅减少了判定次数。

【易错点】 选项 A 错误是因为前面有 if (n==5) return true 保护;选项 B 错误是因为只需判定到 sqrt(n)。

6 题(单选题

下列C++代码用两种方式求解两个正整数的最大公约数,说法错误的是()。

int gcd0(int big, int small) {
    if (big < small) {
        swap(big, small);
    }
    if (big % small == 0) {
        return small;
    }
    return gcd0(small, big % small);
}

int gcd1(int big, int small) {
    if (big < small) {
        swap(big, small);
    }
    for (int i = small; i >= 1; --i) {
        if (big % i == 0 && small % i == 0) {
            return i;
        }
    }
    return 1;
}
A.
gcd0() 函数的时间复杂度为 O(log n)
B.
gcd1() 函数的时间复杂度为 O(n)
C.
一般说来,gcd0() 的效率高于 gcd1()
D.
gcd1() 中的代码 `for (int i = small; i >= 1; --i)` 应该修改为 `for (int i = small; i > 1; --i)`

正确答案D

解析详情

【答案】D

【考点】最大公约数(GCD)算法复杂度

【解析】 gcd0 是辗转相除法,效率极高(对数级复杂度)。gcd1 是蛮力枚举法,从较小数开始向下尝试,必须包含 1,因为互质的两个数最大公约数为 1。如果修改为 i > 1,则对于互质的情况将无法正确返回 1,且循环结束后会多执行一次无效的 return 1。

【易错点】 误以为公约数必然大于 1 而漏掉互质情况的判定。

7 题(单选题

下面的代码用于判断整数n是否是质数,错误的说法是()。

bool is_prime(int n) {
    if (n <= 1) return false;
}

int finish_number = static_cast<int>(sqrt(n)) + 1;
for (int i = 2; i < finish_number; ++i) {
    if (n % i == 0)
        return false;
}
return true;
}
A.
埃氏筛算法相对于上面的代码效率更高
B.
线性筛算法相对于上面的代码效率更高
C.
上面的代码有很多重复计算,因为不是判断单个数是否为质数,故而导致筛选出连续数中质数的效率不高
D.
相对而言,埃氏筛算法比上面代码以及线性筛算法效率都高

正确答案D

解析详情

【答案】D

【考点】质数判定与筛法效率对比

【解析】 线性筛(欧拉筛)在筛选连续区间质数时是目前效率最高的算法(复杂度 O(n)),每个合数只被筛掉一次;埃氏筛复杂度为 O(n log log n),效率略低于线性筛。题干代码是针对单个数字的判定,效率为 O(sqrt(n)),在处理大批量数据时效率最低。

【易错点】 误以为埃氏筛是效率最高的算法,忽略了线性筛的进一步优化。

8 题(单选题

唯一分解定理描述了关于正整数的什么性质?

A.
任何正整数都可以表示为两个素数的和。
B.
任何大于1的合数都可以唯一分解为有限个质数的乘积。
C.
两个正整数的最大公约数总是等于它们的最小公倍数除以它们的乘积。
D.
所有素数都是奇数。

正确答案B

解析详情

【答案】B

【考点】唯一分解定理(算术基本定理)

【解析】 唯一分解定理指出:任何大于 1 的自然数,要么本身是质数,要么可以唯一地分解为有限个质数的乘积。如果不考虑乘积中质数的顺序,这种分解方式是唯一的。

【易错点】 选项 A 是哥德巴赫猜想的内容;选项 D 忽视了偶素数 2。

9 题(单选题

下面的C++代码,用于求一系列数据中的最大值。有关其算法说法错误的是()。

int find_max_recursive(const vector<int>& nums, int left, int right) {
    if (left == right)
        return nums[left];
    int mid = left + (right - left) / 2;
    int left_max = find_max_recursive(nums, left, mid);
    int right_max = find_max_recursive(nums, mid + 1, right);
    return max(left_max, right_max);
}

int find_max(const vector<int>& nums) {
    if (nums.empty()) {
        throw invalid_argument("输入数组不能为空");
    }
    return find_max_recursive(nums, 0, nums.size() - 1);
}
A.
该算法采用分治算法
B.
该算法是递归实现
C.
该算法采用贪心算法
D.
该算法不是递推算法

正确答案C

解析详情

【答案】C

【考点】分治算法与递归

【解析】 代码将数组一分为二,递归求解左右子区间的最大值并取较大者,这是典型的分治思想(Divide and Conquer)。该实现是递归形式,非贪心算法(贪心通常指局部最优选择构建全局最优,此题并无局部选择过程)。

【易错点】 混淆分治和贪心的概念。分治是将问题规模缩小,贪心是做出某种特定的策略选择。

10 题(单选题

下面的 C++ 代码,用于求一系列数据中的最大值。有关其算法说法错误的是()。

int find_max(const vector<int>& nums) {
    if (nums.empty()) {
        throw invalid_argument("输入数组不能为空");
    }

    int max_value = nums[0];
    for (int num : nums) {
        if (num > max_value) {
            max_value = num;
        }
    }
    return max_value;
}
A.
本题 find_max() 函数采用的是迭代算法
B.
本题 find_max() 函数的时间复杂度为 O(n)
C.
和上一题的 find_max() 相比,因为没有递归,所以没有栈的创建和销毁开销
D.
本题 find_max() 函数和上一题的 find_max() 空间复杂度相同

正确答案D

解析详情

【答案】D

【考点】迭代与递归复杂度对比

【解析】 迭代算法的空间复杂度为 O(1),只需一个辅助变量 max_value。上一题的分治递归算法,递归深度为 log n,每一层都需要压栈保存局部变量,因此空间复杂度为 O(log n)。两者空间复杂度并不相同。

【易错点】 只考虑时间复杂度(都是 O(n))而忽视了递归函数调用的系统栈空间开销。

11 题(单选题

下面的 C++ 代码用于在升序数组 lst 中查找目标值 target 最后一次出现的位置。相关说法,正确的是()。

int binary_search_last_occurrence(const vector<int>& lst, int target) {
    if (lst.empty()) return -1;

    int low = 0, high = lst.size() - 1;

    while (low < high) {
        int mid = (low + high + 1) / 2;
        if (lst[mid] <= target) {
            low = mid;
        } else {
            high = mid - 1;
        }
    }

    if (lst[low] == target) {
        return low;
    } else {
        return -1;
    }
}
A.
当 lst 中存在重复的 target 时,该函数总能返回最后一个 target 的位置,即便 lst 全由相同元素组成
B.
当 target 小于 lst 中所有元素时,该函数会返回 0
C.
循环条件改为 `while (low <= high)` 程序执行效果相同,且能提高准确性
D.
将代码中 `(low + high + 1) / 2` 修改为 `(low + high) / 2` 效果相同

正确答案A

解析详情

【答案】A

【考点】二分查找变体:查找最后一次出现位置

【解析】 代码通过 mid = (low+high+1)/2(向上取整)并在满足 lst[mid] <= target 时更新 low = mid,能够将搜索范围持续向右移动,从而锁定最后一个目标位置。选项 D 修改后,在只有两个元素时可能出现 mid = low 导致死循环。

【易错点】 忽视二分查找中向上/向下取整对死循环的影响,以及边界更新策略的重要性。

12 题(单选题

有关下面C++代码的说法,错误的是()。

double sqrt_binary(long long n, double epsilon = 1e-10) {
    if (n < 0) {
        throw invalid_argument("输入必须为非负整数");
    }
    if (n == 0 || n == 1) return n;

    // 阶段 1
    long long low = 1, high = n;
    long long k = 0;
    while (low <= high) {
        long long mid = (low + high) / 2;
        long long mid_sq = mid * mid;
        if (mid_sq == n) {
            return mid;
        } else if (mid_sq < n) {
            k = mid;
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    long long next_k = k + 1;
    if (next_k * next_k == n) {
        return next_k;
    }

    // 阶段 2
    double low_d = (double)k;
    double high_d = (double)(k + 1);
    double mid;

    while (high_d - low_d >= epsilon) {
        mid = (low_d + high_d) / 2;
        double mid_sq = mid * mid;
        if (mid_sq < n) {
            low_d = mid;
        } else {
            high_d = mid;
        }
    }

    double result = (low_d + high_d) / 2;
    long long check_int = (long long)(result + 0.5);
    if (check_int * check_int == n) {
        return check_int;
    }

    return result;
}
A.
“阶段1”的目标是寻找正整数 n 可能的正完全平方根
B.
“阶段2”的目标是如果正整数 n 没有正完全平方根,则在可能产生完全平方根附近寻找带小数点的平方根
C.
代码 check_int = (long long)(result + 0.5) 是检查因浮点误差是否为正完全平方根
D.
阶段2的二分法中 high_d - low_d >= epsilon 不能用于浮点数比较,会进入死循环

正确答案D

解析详情

【答案】D

【考点】二分法求平方根、浮点数精度处理

【解析】 阶段 2 使用 epsilon 作为控制精度的手段是合理的。由于二分法每次区间减半,区间长度 high_d - low_d 必然会在有限次数内小于给定的 epsilon,从而正常退出循环。选项 D 说会死循环是错误的。

【易错点】 虽然直接用 == 比较浮点数有风险,但区间范围的减小是确定性的,不会因此死循环。

13 题(单选题

硬币找零问题中要求找给客户最少的硬币。coins 存储可用硬币规格,单位为角,假设规格都小于10角,且一定有1角规格。amount 为要找零的金额,约定必须为1角的整数倍。输出为每种规格及其数量,按规格从大到小输出,如果某种规格不必要,则输出为0。下面是其实现代码,相关说法正确的是()。

const int MAX_COINS = 10;
int result[MAX_COINS] = {0}; // 假设最多10种面额

int find_coins(vector<int> coins, int amount) {
    sort(coins.begin(), coins.end(), greater<int>());
    int n = coins.size();

    for (int i = 0; i < n; ++i) {
        int coin = coins[i];
        int num = amount / coin;
        result[i] = num;
        amount -= num * coin;
        if (amount == 0) break;
    }

    cout << "找零方案如下:" << endl;
    for (int i = 0; i < n; ++i) {
        cout << coins[i] << "角需要" << result[i] << "枚" << endl;
    }

    return 0;
}
A.
上述代码采用贪心算法实现
B.
针对本题具体要求,上述代码总能找到最优解
C.
上述代码采用枚举算法
D.
上述代码采用分治算法

正确答案A

解析详情

【答案】A

【考点】贪心算法:找零问题

【解析】 代码的核心逻辑是每次选取面额最大的硬币并尽可能多地使用(num = amount / coin),这符合贪心算法的“局部最优”特征。虽然在通用硬币面额下贪心不一定能得全局最优,但本题要求指出算法类型,选项 A 最准确。

【易错点】 混淆算法实现方式与算法正确性,选项 B 虽然在某些币值组合下成立,但在非标币值下贪心并非最优解,不具通用性。

14 题(单选题

关于下述C++代码的快速排序算法,说法错误的是()。

int randomPartition(std::vector<int>& arr, int low, int high) {
    int random = low + rand() % (high - low + 1);
    std::swap(arr[random], arr[high]);

    int pivot = arr[high];
    int i = low - 1;

    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = randomPartition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
A.
在 randomPartition 函数中,变量 i 的作用是记录大于基准值的元素的边界
B.
randomPartition 函数随机选择基准值,可以避免输入数据特定模式导致的最坏情况下时间复杂度O(n2)O(n^{2})
C.
快速排序平均时间复杂度是O(nlogn)O(n \log n)
D.
快速排序是稳定排序算法

正确答案D

解析详情

【答案】D

【考点】快速排序特性与复杂度

【解析】 快速排序在划分过程中会进行远距离的元素交换(swap),这可能会改变相同数值元素的原始相对位置,因此它是不稳定的排序算法。选项 A, B, C 均描述了快排的正确原理或复杂度。

【易错点】 区分稳定排序(如归并、冒泡)与非稳定排序(如快排、堆排、选择)。

15 题(单选题

小杨编写了一个如下的高精度除法函数,则横线上应填写的代码为()。

const int MAXN = 1005; // 最大位数
struct BigInt {
    int d[MAXN]; // 存储数字, d[0]是个位, d[1]是十位, ...
    int len; // 数字长度

    BigInt() {
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

// 比较两个高精度数的大小

int compare(BigInt a, BigInt b) {
    if (a.len != b.len) return a.len > b.len ? 1 : -1;
    for (int i = a.len - 1; i >= 0; i--) {
        if (a.d[i] != b.d[i]) return a.d[i] > b.d[i] ? 1 : -1;
    }
    return 0;
}

// 高精度减法

BigInt sub(BigInt a, BigInt b) {
    BigInt c;
    for (int i = 0; i < a.len; i++) {
        c.d[i] += a.d[i] - b.d[i];
        if (c.d[i] < 0) {
            c.d[i] += 10;
            c.d[i + 1]--;
        }
    }
    c.len = a.len;
    while (c.len > 1 && c.d[c.len - 1] == 0) c.len--;
    return c;
}

// 高精度除法 (a/b, 返回商和余数)

pair<BigInt, BigInt> div(BigInt a, BigInt b) {
    BigInt q, r; // q是商, r是余数

    if (compare(a, b) < 0) { // 如果a<b, 商为0, 余数为a
        q.len = 1;
        q.d[0] = 0;
        r = a;
        return make_pair(q, r);
    }

    // 初始化余数r为a的前b.len位
    r.len = b.len;
    for (int i = a.len - 1; i >= a.len - b.len; i--) {
        r.d[i - (a.len - b.len)] = a.d[i];
    }

    // 逐位计算商
    for (int i = a.len - b.len; i >= 0; i--) {
        // 把下一位加入余数
        if (r.len > 1 || r.d[0] != 0) {
            for (int j = r.len; j > 0; j--) {
                r.d[j] = r.d[j - 1];
            }
            ___
        } else {
            r.d[0] = a.d[i];
            r.len = 1;
        }

        // 计算当前位的商
        while (compare(r, b) >= 0) {
            r = sub(r, b);
            q.d[i]++;
        }
    }

    // 确定商的长度
    q.len = a.len - b.len + 1;
    while (q.len > 1 && q.d[q.len - 1] == 0) q.len--;

    // 处理余数前导零
    while (r.len > 1 && r.d[r.len - 1] == 0) r.len--;

    return make_pair(q, r);
}
A.
r.d[0] = a.d[i];
r.len++;
B.
r.d[i] = a.d[i];
r.len++;
C.
r.d[i] = a.d[i];
r.len = 1;
D.
r.d[0] = a.d[i];
r.len = 1;

正确答案A

解析详情

【答案】A

【考点】高精度除法:长除法模拟

【解析】 模拟手算除法,当计算下一位商时,需要将当前的余数 r 左移一位(乘以 10),然后把被除数的下一位 a.d[i] 补到余数的个位上。代码前面的循环已将 r.d 向高位移动,横线处需填入将新位存入 r.d[0] 并使长度自增的操作。

【易错点】 低位补位操作错误,或者误以为 r 长度保持不变。

判断题(每题 2 分)

1 题(判断题

下面C++代码是用欧几里得算法(辗转相除法)求两个正整数的最大公约数,a大于b还是小于b都适用。

int gcd(int a, int b) {
    while (b) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

正确答案正确

解析详情

【答案】正确

【考点】欧几里得算法原理

【解析】 辗转相除法公式为 gcd(a, b) = gcd(b, a % b)。如果初始时 a < b,第一次循环 b = a % b = a,a = temp = b,相当于自动交换了 a 和 b 的位置。因此代码对输入大小关系没有限制。

【易错点】 误以为必须手动保证 a > b 算法才能工作。

2 题(判断题

假设函数 gcd() 能正确求两个正整数的最大公约数,则下面的 lcm() 函数能求相应两数的最小公倍数。

int lcm(int a, int b) {
    return a * b / gcd(a, b);
}

正确答案正确

解析详情

【答案】正确

【考点】GCD 与 LCM 的关系

【解析】 根据数论性质,两个数 a 和 b 的乘积等于它们的最大公约数与最小公倍数的乘积,即 a * b = gcd(a, b) * lcm(a, b)。因此 lcm = (a * b) / gcd。

【易错点】 虽然公式正确,但在大数情况下直接 a * b 可能产生溢出,稳妥写法是 a / gcd(a, b) * b。

3 题(判断题

下面的C++代码用于输出每个数对应的质因数列表,输出形如:{5: [5], 6: [2, 3], 7: [7], 8: [2, 2, 2]}。

int main() {
    int n, m;
    cin >> n >> m;
    if (n > m) swap(n, m);
    map<int, vector<int>> prime_factor;
    for (int i = n; i <= m; ++i) {
        int j = 2, k = i;
        while (k != 1) {
            if (k % j == 0) {
                prime_factor[i] = prime_factor[i] + j;
                k / = j;
            } else {
                ++j;
            }
        }
    }
    for (auto& p : prime_factor) {
        cout << p.first << "";
        for (int v : p.second) {
            cout << v << "";
            cout << endl;
        }
    }
    return 0;
}

正确答案错误

解析详情

【答案】错误

【考点】C++ 语法:vector 运算与运算符

【解析】 代码中有多处语法错误:1. `prime_factor[i] + j` 试图对 vector 使用加法运算符,C++ 标准库中不支持将 int 直接加到 vector 上(应使用 push_back);2. `k / = j` 中间有空格,不是合法的复合赋值运算符。此外输出格式也不符合题干描述的 JSON 风格。

【易错点】 误以为 vector 可以像某些动态语言一样直接用 + 连接元素。

4 题(判断题

下面的C++代码实现归并排序。代码在执行时,将输出一次 HERE 字符串,因为merge()函数仅被调用一次。

void merge(std::vector<int>& arr, int left, int mid, int right) {
    std::vector<int> temp(right - left + 1);

    int i = left;
    int j = mid + 1;
    int k = 0;

    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }

        while (i <= mid) {
            temp[k++] = arr[i++];
        }
    }

    while (j <= right) {
        temp[k++] = arr[j++];
    }

    for (int p = 0; p < k; ++p) {
        arr[left + p] = temp[p];
    }
}
void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left >= right) {
        return;
    }
    int mid = left + (right - left) / 2;
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    std::cout << "HERE";
    merge(arr, left, mid, right);
}

正确答案错误

解析详情

【答案】错误

【考点】归并排序:递归调用次数

【解析】 归并排序采用分治递归。每一层的 `mergeSort` 都会递归调用自身处理左右半部分,并在合并时调用 `merge`。对于大小为 n 的数组,总共会进行 n-1 次合并操作。因此 "HERE" 会输出多次(具体为 n-1 次),而非仅一次。

【易错点】 忽视了递归函数的深层调用,误以为只有最顶层的一次执行。

5 题(判断题

归并排序的最好、最坏和平均时间复杂度均为O(nlogn)O(n \log n)

正确答案正确

解析详情

【答案】正确

【考点】归并排序复杂度特性分析

【解析】 归并排序的执行过程始终是均匀地将数组一分为二,直到规模为 1,然后再逐层合并。无论输入数据初始状态如何(已序、逆序或随机),其递归深度总是 log n,每一层合并的总时间总是 O(n),因此三者复杂度均为 O(n log n)。

【易错点】 与快速排序混淆,快排在最坏情况下会退化为 O(n^2)。

6 题(判断题

查字典这个小学生必备技能,可以把字典视为一个已排序的数组。假设小杨要查找一个音音字母为 g 的单词,他首先翻到字典约一半的页数,发现该页的首字母是 m,由于字母表中 g 位于 m 之前,所以排除字典后半部分,查找范围缩小到前半部分;不断重复上述步骤,直至找到首字母为 g 的页码。这种查字典的一系列操作可看作二分查找。

正确答案正确

解析详情

【答案】正确

【考点】二分查找生活实例映射

【解析】 该过程完全符合二分查找(Binary Search)的特征:1. 查找对象是有序的(字典按字母排序);2. 每次取中间位置与目标比较;3. 根据比较结果舍弃一半的搜索范围。

【易错点】 如果翻书的位置是随机或凭直觉偏移的(插值查找),则不完全等同于标准二分查找,但题干描述了“约一半”和“重复上述步骤”,可视为二分查找。

7 题(判断题

求解下图中A点到D点最短路径,其中A到B之间的12可以理解为距离。求解这样的问题常用 Dijkstra 算法,其思路是通过逐步选择当前距离起点最近的节点来求解非负权重图(如距离不能为负值)单源最短路径。从该算法的描述可以看出,Dijkstra 算法是贪心算法。

Image

正确答案正确

解析详情

【答案】正确

【考点】Dijkstra 算法本质分析

【解析】 Dijkstra 算法在每一步都从未访问的节点中选择距离源点最近的一个加入集合,并以此节点进行松弛操作。这种“当前最优”的选择策略是典型的贪心思想。由于路径权重非负,这种局部最优最终能推导出全局最优最短路径。

【易错点】 注意 Dijkstra 算法不能处理负权边,因为负权边会破坏该算法的贪心基础。

8 题(判断题

分治算法将原问题可以分解成规模更小的子问题,使得求解问题的难度降低。但由于分治算法需要将问题进行分解,并且需要将多个子问题的解合并为原问题的解,所以分治算法的效率通常比直接求解原问题的效率低。

正确答案错误

解析详情

【答案】错误

【考点】分治算法效率评估

【解析】 分治算法的初衷通常是为了提高效率。许多经典高效算法(如 O(n log n) 的快速排序、归并排序,或大整数乘法的 Karatsuba 算法)都利用分治将高复杂度的直接计算降低到了更低级别。断言其效率比直接求解低是错误的。

【易错点】 误以为分解和合并的开销总是会超过规模缩小带来的收益。

9 题(判断题

函数 puzzle 定义如下,则调用 puzzle(7) 程序会无限递归。

int puzzle(int n) {
    if (n == 1) return 1;
    if (n % 2 == 0) return puzzle(n / 2);
    return puzzle(3 * n + 1);
}

正确答案错误

解析详情

【答案】错误

【考点】递归终止、考拉兹猜想(3n+1问题)

【解析】 这是著名的考拉兹猜想模拟:7 是奇数 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1。由于序列最终达到了 1,触发了 if (n == 1) 的终止条件,因此程序会正常返回,不会无限递归。

【易错点】 误以为 3n+1 会使数值不断增大而无法收敛到 1。

10 题(判断题

如下为线性筛法,用于高效生成素数表,其核心思想是每个合数只被它的最小质因数筛掉一次,时间复杂度为O(n)O(n)

for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) {
is_prime[i * primes[j]] = false;
if (i % primes[j] == 0) {
    break;
}
}
}
}
return primes;
}

正确答案正确

解析详情

【答案】正确

【考点】线性筛(欧拉筛)效率分析

【解析】 线性筛的关键在于 if (i % primes[j] == 0) break; 这一句,它保证了每个合数都被且仅被其最小质因数标记。因此每个数字被处理的次数是常数级别的,整体时间复杂度为严格的 O(n)。

【易错点】 容易与埃氏筛 O(n log log n) 的复杂度混淆。