GESP6 · 2025年12月真题

客观题逐题精讲

15 道单选 + 10 道判断。每题保留题干、选项、答案、考点、分步推导和易错提醒,适合投影讲评与课后复习。

一、考点地图

二叉排序树(BST)4 题
动态规划3 题
虚函数3 题
递归2 题
队列2 题
构造函数2 题
面向对象编程2 题
运行时多态2 题
2 题
0/1 背包问题1 题
编码特性1 题
编译检查1 题

二、单选题逐题讲

单选题 1虚函数;构造函数;运行时多态

在面向对象编程中,下列关于虚函数的描述中,错误的是()。

  1. A虚函数用于支持运行时多态
  2. B通过基类指针调用虚函数时,会根据对象实际类型决定调用版本
  3. C构造函数可以声明为虚函数以支持多态
  4. D基类析构函数常声明为虚函数以避免资源泄漏
展开解析
答案:C
虚函数构造函数运行时多态
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 虚函数通过虚函数表实现动态绑定,用于支持多态。
  2. 构造函数在执行时,对象的虚函数表指针尚未完全初始化,且构造函数不需要多态语义,因此 C++ 不允许构造函数为虚函数。
  3. 析构函数设为虚函数是为了确保派生类对象能被正确释放。
易错点:混淆构造函数与析构函数的虚函数性质。
单选题 2面向对象编程;运行时多态;虚函数

执行如下代码,会输出 钢琴:叮咚叮咚 和 吉他:咚咚当当 。这体现了面向对象编程的( )特性。

class Instrument {
    public:
        virtual void play() {
            cout << "乐器在演奏声音" << endl;
    }

    virtual ~Instrument() {}
};

class Piano : public Instrument {
    public:
        void play() override {
            cout << "钢琴:叮咚叮咚" << endl;
    }
};

class Guitar : public Instrument {
    public:
        void play() override {
            cout << "吉他:咚咚当当" << endl;
    }

    int main() {
        Instrument* instruments[2];
        instruments[0] = new Piano();
        instruments[1] = new Guitar();

        for (int i = 0; i < 2; ++i) {
            instruments[i]->play();
        }

        for (int i = 0; i < 2; ++i) {
            delete instruments[i];
        }
        return 0;
    }
}
  1. A继承
  2. B封装
  3. C多态
  4. D链接
展开解析
答案:C
面向对象编程运行时多态虚函数
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 代码中通过基类指针 Instrument* 调用虚函数 play()。
  2. 在运行时,程序会根据指针实际指向的对象类型(Piano 或 Guitar)执行对应的成员函数。
  3. 这种“一个接口,多种实现”的现象即为多态。
易错点:容易将继承关系误认为本题体现的核心特性。
单选题 3虚函数;override 关键字;编译检查

关于以下代码,说法正确的是()。

class Instrument {
    public:
    void play() {
        cout << "乐器在演奏声音" << endl;
    }

    virtual ~Instrument() {}
};

class Piano : public Instrument {
    public:
    void play() override {
        cout << "钢琴:叮咚叮咚" << endl;
    }
};

class Guitar : public Instrument {
    public:
    void play() override {
        cout << "吉他:咚咚当当" << endl;
    }

    int main() {
        Instrument* instruments[2];
        instruments[0] = new Piano();
        instruments[1] = new Guitar();

        for (int i = 0; i < 2; ++i) {
            instruments[i]->play();
        }

        for (int i = 0; i < 2; ++i) {
            delete instruments[i];
        }
        return 0;
    }
}
  1. A执行代码会输出两行,内容分别为:钢琴:叮咚叮咚 和 吉他:咚咚当当
  2. B执行代码会输出两行,内容分别为:乐器在演奏声音 和 乐器在演奏声音
  3. C代码编译出现错误
  4. D代码运行出现错误
展开解析
答案:C
虚函数override 关键字编译检查
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 在 C++ 中,override 关键字用于显式声明重写基类的虚函数。
  2. 如果基类中对应的函数没有被声明为 virtual,编译器会报错。
  3. 本题基类 play() 缺少 virtual 关键字,因此派生类的 override 声明会导致编译失败。
易错点:忽略 override 对基类虚函数声明的强制依赖。
单选题 4栈;后进先出(LIFO);撤销操作

某文本编辑器把用户输入的字符依次压入栈 S。用户依次输入 A,B,C,D 后,用户按了两次撤销(每次撤销,弹出栈顶一个字符)。此时栈从栈底到栈顶的内容是:()。

  1. AA B
  2. BA B C
  3. CA B D
  4. DB C
展开解析
答案:A
后进先出(LIFO)撤销操作
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 栈遵循后进先出原则。
  2. 依次入栈 A、B、C、D 后,栈顶为 D。
  3. 撤销操作相当于弹出栈顶:第一次撤销弹出 D,第二次撤销弹出 C。
  4. 最后栈内剩余元素从底到顶依次为 A、B。
易错点:将撤销操作误解为从栈底删除。
单选题 5循环队列;取模运算;指针更新

假设循环队列数组长度为 N,其中队空判断条件为:front == rear,队满判断条件为:(rear + 1) % N == front,出队对应的操作为:front = (front + 1) % N,入队对于的操作为:rear = (rear + 1) % N。循环队列长度 N = 6,初始 front = 1,rear = 1,执行操作序列为:入队,入队,入队,出队,入队,入队,则最终(front,rear)的值是()。

  1. A(2, 5)
  2. B(2, 0)
  3. C(3, 5)
  4. D(3, 0)
展开解析
答案:B
循环队列取模运算指针更新
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 初始 front=1, rear=1。
  2. 操作序列:入队(1,2)、入队(1,3)、入队(1,4)、出队(2,4)、入队(2,5)、入队(2,0)(注:(5+1)%6=0)。
  3. 最终状态为 (2, 0)。
  4. 计算过程中需严格按照 (rear + 1) % N 更新指针。
易错点:模运算边界处理,如 5+1 取模后应回到 0。
单选题 6二叉树分类;层序遍历;完全二叉树判定

以下函数 check() 用于判断一棵二叉树是否为()。

bool check(TreeNode* root) {
    if (!root) return true;

    queue<TreeNode*> q;
    q.push(root);
    bool hasNull = false;
    while (!q.empty()) {
        TreeNode* cur = q.front(); q.pop();
        if (!cur) {
            hasNull = true;
        } else {
            if (hasNull) return false;
            q.push(cur->left);
            q.push(cur->right);
        }
    }
    return true;
}
  1. A满二叉树
  2. B完全二叉树
  3. C二叉搜索树
  4. D平衡二叉树
展开解析
答案:B
二叉树分类层序遍历完全二叉树判定
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 该函数使用队列进行层序遍历。
  2. hasNull 变量记录是否遇到了空结点。
  3. 在完全二叉树中,空结点只能出现在序列末尾。
  4. 如果遇到空结点后又出现了非空结点(hasNull 为真且 cur 不为空),则说明不是完全二叉树。
易错点:分不清完全二叉树和满二叉树的层序特征。
单选题 7二叉树遍历;递归;后序遍历

以下代码实现了二叉树的()。

void traverse(TreeNode* root) {
    if (!root) return;
    traverse(root->left);
    traverse(root->right);
    cout << root->val << " ";
}
  1. A前序遍历
  2. B中序遍历
  3. C后序遍历
  4. D层序遍历
展开解析
答案:C
二叉树遍历递归后序遍历
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 递归函数的执行顺序是:先递归左子树,再递归右子树,最后访问根结点(输出 val)。
  2. 这种“左-右-根”的访问顺序定义为后序遍历。
易错点:将递归调用的顺序与访问结点的时机混淆。
单选题 8哈夫曼树;贪心算法;内部结点构造

下面代码实现了哈夫曼编码,则横线处应填写的代码是()。

struct Symbol {
    char ch; //字符
    long long freq; //频率
    string code; //哈夫曼编码
};

struct Node {
    long long w; //权值
    int l, r; //左右孩子(节点下标),-1 表示空
    int sym; //叶子对应符号下标;内部节点为 -1
    Node(long long _w=0, int _l=-1, int _r=-1, int _sym=-1)
        : w(_w), l(_l), r(_r), sym(_sym) {}
};
// 从 A(leafIdx) 和 B(internalIdx) 的队首取最小的一个节点下标
static int PopMinNode(const vector<Node>& nodes,
                             const vector<int>& leafIdx, int n, int& pA,
                             const vector<int>& internalIdx, int& pB) {
        if (pA < n && (pB >= (int)internalIdx.size() ||
                   nodes[leafIdx[pA]].w <= nodes[internalIdx[pB]].w)) {
            return leafIdx[pA++];
        }
        else {
            return internalIdx[pB++];
        }
    }

// DFS 生成编码(左 0,右 1)
static void DFSBuildCodes(int u, const vector<Node>& nodes, Symbol sym[], string& path) {
        if (u == -1) return;

    if (nodes[u].sym != -1) { //叶子
        sym[nodes[u].sym].code = path;
        return;
    }

    path.push_back('0');
    DFSBuildCodes(nodes[u].l, nodes, sym, path);
    path.pop_back();

    path.push_back('1');
    DFSBuildCodes(nodes[u].r, nodes, sym, path);
    path.pop_back();
}

int BuildHuffmanCodes(Symbol sym[], int n) {
    for (int i = 0; i < n; i++) sym[i].code.clear();
    if (n <= 0) return -1;

    // 只有一个字符:约定编码为 "0"
    if (n == 1) {
        sym[0].code = "0";
        return 0;
    }

    vector<Node> nodes;
    nodes.reserve(2 * n);

    // 1) 建立叶子节点
    vector<int> leafIdx(n);
    for (int i = 0; i < n; i++) {
        leafIdx[i] = (int)nodes.size();
        nodes.push_back(Node(sym[i].freq, -1, -1, i));
    }

// 2) 叶子按权值排序 (A 队列)
sort(leafIdx.begin(), leafIdx.end(),
    [&](int a, int b) {
        if (nodes[a].w != nodes[b].w) return nodes[a].w < nodes[b].w;
        return nodes[a].sym < nodes[b].sym; // 稳定一下
    });

    // B 队列 (内部节点下标队列)
    vector<int> internalIdx;
    internalIdx.reserve(n);

    int pA = 0, pB = 0;

    // 3) 合并 n-1 次
    for (int k = 1; k < n; k++) {
        int x = PopMinNode(nodes, leafIdx, n, pA, internalIdx, pB);
        int y = PopMinNode(nodes, leafIdx, n, pA, internalIdx, pB);

        int z = (int)nodes.size();

        _____________// 在此处填写代码
    }

    int root = internalIdx.back();

    // 4) DFS 生成编码
    string path;
    DFSBuildCodes(root, nodes, sym, path);
    return root;
}
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
internalIdx.push_back(z);
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
leafIdx.push_back(z);
internalIdx.push_back(z);
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x + y));
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x+y));
leafIdx.push_back(z);
  1. Anodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1)); internalIdx.push_back(z);
  2. Bnodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1)); leafIdx.push_back(z);
  3. CinternalIdx.push_back(z); nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x + y));
  4. Dnodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x+y)); leafIdx.push_back(z);
展开解析
答案:A
哈夫曼树贪心算法内部结点构造
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 构造哈夫曼树时,每次取两个权值最小的结点合并。
  2. 新生成的内部结点权值为两者之和,其左右孩子指向选出的两结点,且内部结点不直接对应符号(sym = -1)。
  3. 最后需将新结点下标 z 加入内部结点队列。
易错点:误将内部结点下标加入叶子结点队列 leafIdx。
单选题 9哈夫曼编码;前缀码性质;变长编码

以下关于哈夫曼编码的说法,正确的是()。

  1. A哈夫曼编码是定长编码
  2. B哈夫曼编码中,没有任何一个字符的编码是另一个字符编码的前缀
  3. C哈夫曼编码一定唯一
  4. D哈夫曼编码不能用于数据压缩
展开解析
答案:B
哈夫曼编码前缀码性质变长编码
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 哈夫曼编码是一种变长编码。
  2. 其核心性质是前缀码,即任一字符的编码都不是另一个字符编码的前缀,从而保证解码的唯一性。
  3. 哈夫曼编码是根据频率构造的,不一定唯一,且主要用于数据压缩。
易错点:错误认为最优编码结果是唯一的。
单选题 10二叉排序树(BST);递归算法;结点插入

以下函数实现了二叉排序树(BST)的()操作。

TreeNode* op(TreeNode* root, int x) {
    if (!root) return new TreeNode(x);
    if (x < root->val)
        root->left = op(root->left, x);
    else
        root->right = op(root->right, x);
    return root;
}
  1. A查找
  2. B插入
  3. C删除
  4. D遍历
展开解析
答案:B
二叉排序树(BST)递归算法结点插入
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 函数通过递归比较 x 与当前结点值的大小。
  2. 如果 x 较小则去左子树,较大则去右子树。
  3. 当遇到空指针时,新建结点并返回,这实现了将新值插入到 BST 合适位置的操作。
易错点:将具有查找逻辑的插入操作误认为是单纯的查找。
单选题 11深度优先搜索(DFS);栈;前序遍历实现

下列代码实现了树的深度优先遍历,则横线处应填入()。

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};

void dfs(TreeNode* root) {
    if (!root) return;
    stack<TreeNode*> st;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top(); st.pop();
        cout << node->val << " ";
        if (node->right) st.push(node->right);
        ___________
   }
}
  1. Aif (node->left) st.push(node->left);
  2. Bif (node->left) st.pop(node->left);
  3. Cif (node->left) st.front(node->left);
  4. Dif (node->left) st.push(node->right);
展开解析
答案:A
深度优先搜索(DFS)前序遍历实现
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 使用栈模拟 DFS 遍历时,为了保证左子树先于右子树被处理,由于栈是后进先出,必须先压入右孩子,再压入左孩子。
  2. 因此横线处应为压入左孩子的操作。
易错点:在栈操作中错误地先压入左孩子。
单选题 12广度优先搜索(BFS);队列;二叉树搜索

给定一棵普通二叉树(节点值没有大小规律),下面代码判断是否存在值为 x 的结点,则横线处应填入()。

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};

TreeNode* bfsFind(TreeNode* root, int x) {
    if (!root) return nullptr;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        TreeNode* cur = q.front(); q.pop();
        if (cur->val == x) return cur;
        ___________
  }
    return nullptr;
}
if (cur->left) q.push(cur->left);
if (cur->right) q.push(cur->right);
q.push(cur->left);
q.push(cur->right);
  1. Aq.push(cur);
  2. Bif (cur->right) q.push(cur->right);
  3. Cif (cur->left) q.push(cur->left); if (cur->right) q.push(cur->right);
  4. Dq.push(cur->left); q.push(cur->right);
展开解析
答案:C
广度优先搜索(BFS)队列二叉树搜索
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. BFS 查找的核心是使用队列层序扩展。
  2. 从队列取出当前结点检查值后,若非目标值,则需将其存在的左、右孩子依次入队。
  3. 只有这样才能遍历到树中所有结点。
  4. 选项 C 包含了必要的空指针检查。
易错点:漏掉子结点入队前的空指针判断。
单选题 13二叉排序树(BST);最坏时间复杂度;退化树

在二叉排序树(Binary Search Tree, BST)中,假设节点值互不相同。给定如下搜索函数,以下说法一定正确的是()。

bool find(Node* root, int x) {
    while (root) {
        if (root->val == x) return true;
        root = (x < root->val) ? root->left : root->right;
    }
    return false;
}
  1. A最坏情况下,访问结点数是 𝑂 ( log ⁡ 𝑛 ) O(logn)
  2. B最坏情况下,访问结点数是 𝑂 ( 𝑛 ) O(n)
  3. C无论如何,访问结点数都不超过树高的一半
  4. D一定比在普通二叉树中搜索快
展开解析
答案:B
二叉排序树(BST)最坏时间复杂度退化树
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. BST 的查找效率取决于树的高度。
  2. 在最坏情况下,BST 可能退化为单支树(类似于链表),此时树高为 n,查找一个值需要比较 n 次,即时间复杂度为 O(n)
易错点:混淆平均时间复杂度 O(log n) 与最坏时间复杂度。
单选题 140/1 背包问题;动态规划;一维空间优化

0/1 背包(每件物品最多选一次)问题通常可用一维动态规划求解,核心代码如下。则下面说法正确的是()。

for each item (w, v):
for (int j = W; j >= w; --j)
    dp[j] = max(dp[j], dp[j-w] + v);
  1. A内层 j 必须从小到大,否则会漏解
  2. B内层 j 必须从大到小,否则同一件物品会被用多次
  3. Cj 从大到小或从小到大都一样
  4. D只要 dp 初始为 0,方向无所谓
展开解析
答案:B
0/1 背包问题动态规划一维空间优化
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 在 0/1 背包的一维实现中,外层循环遍历物品,内层循环遍历容量。
  2. 为了确保每件物品只被选一次,更新 dp[j] 时必须使用上一轮物品的状态,因此 j 必须从大到小逆序遍历。
  3. 正序遍历会导致同一物品被多次选取。
易错点:不理解容量循环方向与物品选取次数的关系。
单选题 15动态规划;计算复杂度;状态转移

以下关于动态规划的说法中,错误的是()。

  1. A动态规划方法通常能够列出递推公式。
  2. B动态规划方法的时间复杂度通常为状态的个数。
  3. C动态规划方法有递推和递归两种实现形式。
  4. D对很多问题,递推实现和递归实现动态规划方法的时间复杂度相当。
展开解析
答案:B
动态规划计算复杂度状态转移
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 动态规划的时间复杂度通常等于“状态总数 × 单个状态转移的复杂度”。
  2. 如果每个状态的转移需要遍历(如区间 DP 或背包问题),总复杂度将高于状态个数。
  3. 因此 B 选项中“复杂度通常为状态个数”的表述是错误的。
易错点:忽略了状态转移过程中的额外枚举代价。

三、判断题逐题讲

判断题 1构造函数;拷贝构造函数;对象初始化

以下代码中,构造函数被调用的次数是1次。

class Test {
    public:
        Test() { cout << "T"; }
    };

    int main() {
        Test a;
        Test b = a;
    }
}
  1. 正确正确
  2. 错误错误
展开解析
答案:错误
构造函数拷贝构造函数对象初始化
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. Test a; 调用一次默认构造函数。
  2. Test b = a; 使用 a 初始化 b,调用一次拷贝构造函数。
  3. 虽然代码未定义,编译器会提供默认实现。
  4. 因此总共调用了两次构造相关函数,而非一次。
易错点:忽略了拷贝构造函数也是构造过程的一部分。
判断题 2面向对象编程;封装;信息隐藏

面向对象编程中,封装是指将数据和操作数据的方法绑定在一起,并对外隐藏实现细节。

  1. 正确正确
  2. 错误错误
展开解析
答案:正确
面向对象编程封装信息隐藏
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 封装是面向对象的三大特性之一。
  2. 它将属性(数据)和行为(方法)组合在一个类中,并通过访问修饰符限制外部直接访问内部实现,仅通过接口交互,从而提高安全性。
易错点:仅理解为“代码归类”,而忽略了“对外隐藏”的关键点。
判断题 3二叉树算法;递归;叶子结点判定

以下代码能够正确统计二叉树中叶子结点的数量。

int countLeaf(TreeNode* root) {
    if (!root) return 0;
    if (!root->left && !root->right) return 1;
    return countLeaf(root->left) + countLeaf(root->right);
}
  1. 正确正确
  2. 错误错误
展开解析
答案:正确
二叉树算法递归叶子结点判定
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 代码通过递归统计叶子结点:若为空则返回 0;
  2. 若左右子树皆为空(符合叶子结点定义),则返回 1;
  3. 否则递归累加左右子树的结果。
  4. 逻辑严密,可以正确统计。
易错点:在递归出口处对叶子结点判定条件的理解。
判断题 4广度优先遍历(BFS);队列;数据结构应用

广度优先遍历二叉树可用栈来实现。

  1. 正确正确
  2. 错误错误
展开解析
答案:错误
广度优先遍历(BFS)队列数据结构应用
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 广度优先遍历(BFS)是按层访问的,符合“先见到的结点先访问”的特性,应使用队列(先进先出)。
  2. 栈(后进先出)通常用于实现深度优先遍历(DFS)。
易错点:将 BFS 和 DFS 对应的数据结构混淆。
判断题 5函数调用机制;调用栈;系统内存管理

函数调用管理可用栈来管理。

  1. 正确正确
  2. 错误错误
展开解析
答案:正确
函数调用机制调用栈系统内存管理
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 在计算机系统中,函数调用过程中的参数传递、返回地址保存及局部变量分配都是通过“栈”这一数据结构管理的。
  2. 这符合后进先出的调用逻辑,即最后调用的函数最先返回。
易错点:将系统底层的调用栈与程序中的 std::stack 概念剥离。
判断题 6二叉排序树(BST);最小值性质;树结构搜索

在二叉排序树(BST)中,若某结点的左子树为空,则该结点一定是整棵树中的最小值结点。

  1. 正确正确
  2. 错误错误
展开解析
答案:错误
二叉排序树(BST)最小值性质树结构搜索
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. BST 的最小值位于整棵树最左侧的结点。
  2. 虽然某个结点左子树为空说明它是其子树中最小的,但如果该结点本身是某个祖先结点的右孩子,那么该祖先结点及其左子树中必然存在更小的值。
易错点:将“子树内最小”等同于“全局最小”。
判断题 7二叉排序树(BST);合法性判定;范围约束

下面的函数能正确判断一棵树是不是二叉排序树(左边的数字要比当前数字小,右边的数字要比当前数字大)。

bool isBST(TreeNode* root, int minVal, int maxVal) {
    if (!root) return true;
    if (root->val <= minVal || root->val >= maxVal)
        return false;
    return isBST(root->left, minVal, root->val) &&
        isBST(root->right, root->val, maxVal);
}
  1. 正确正确
  2. 错误错误
展开解析
答案:正确
二叉排序树(BST)合法性判定范围约束
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 判断 BST 不能仅看父子关系,必须保证左子树所有结点都小于根,右子树所有结点都大于根。
  2. 该函数通过传递 minVal 和 maxVal 约束了整棵子树的取值范围,逻辑是正确的。
易错点:忽视了 BST 定义中对全局路径范围的要求。
判断题 8格雷编码;编码特性;循环码

格雷编码相邻两个编码之间必须有多位不同,以避免数据传输错误。

  1. 正确正确
  2. 错误错误
展开解析
答案:错误
格雷编码编码特性循环码
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 格雷码(Gray Code)最核心的特性是相邻两个代码之间只有一位二进制数不同。
  2. 这种特性可以有效避免多位突变引起的瞬时错误。
  3. 题干描述“必须有多位不同”与事实相反。
易错点:记反了格雷码“最小变化”的设计初衷。
判断题 9动态规划;最小路径代价;手动推演

小杨在玩一个闯关游戏,从第1关走到第4关。每一关的体力消耗如下(下标表示关卡编号): cost = [0, 3, 5, 2, 4],其中 cost[i] 表示到达第 i 关需要消耗的体力, cost[0]=0 表示在开始状态,体力消耗为 0。小杨每次可以从当前关卡前进1步或2步。按照上述规则,从第1关到第4关所需消耗的最小体力为7。

  1. 正确正确
  2. 错误错误
展开解析
答案:错误
动态规划最小路径代价手动推演
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 到达第 1 关已经在起点。
  2. 从 1 关到 4 关:根据最优子结构,dp[1]=0, dp[2]=cost[2]=5, dp[3]=cost[3]+min(dp[1],dp[2])=2+0=2, dp[4]=cost[4]+min(dp[2],dp[3])=4+2=6
  3. 最小体力应为 6,非 7。
易错点:对关卡索引和体力消耗计入时机的理解偏差。
判断题 10完全二叉树;树高计算;地板函数

假定只有一个根节点的树的深度为 1,则一棵有 n 个节点的完全二叉树,则树的深度为 ⌊ log ⁡ 2 ( 𝑛 ) ⌋ + 1 ⌊log 2 ​ (n)⌋+1。

  1. 正确正确
  2. 错误错误
展开解析
答案:正确
完全二叉树树高计算地板函数
思考路径:先看这是概念判断、代码阅读还是计算题;再把题干限制拆成 2-4 个小步骤,最后对照选项排除常见误区。

分步推导

  1. 对于有 n 个结点的完全二叉树,如果根深度为 1,则深度 h 的公式为 floor(log2(n)) + 1。
  2. 例如 n=1 时深度为 1,n=3 时深度为 2,均符合该公式。
易错点:记混 floor 和 ceil 在树高公式中的使用条件。
答案速查表

单选题

1C2C3C4A5B6B7C8A9B10B11A12C13B14B15B

判断题

1错误2正确3正确4错误5正确6错误7正确8错误9错误10正确