海致星图招聘 数据库内核研发实习生 一轮笔试 总结复盘(1) 作答语言:C/C++ 链表 二叉树

文章目录

前言

考试方式是邮箱发送网址,进行牛客网线上笔试,四道编程题目,两道标准算法题目,两道实际应用型算法题,本篇博客分享前两道题。(四道题全部写在一起篇幅太大了)

题目1 旋转链表

题目链接:牛客 NC211 旋转链表

题目描述:

算法分析

链表的旋转操作若直接逐节点移动,会因重复遍历导致时间复杂度达到 O ( k ∗ n ) O(k*n) O(k∗n),这在 k 较大时完全不可行。结合链表的特性,我们可将其转化为环形结构来简化操作:先遍历链表统计长度,将尾节点指向头节点形成环,此时旋转 k 个位置的本质,是找到新头节点的位置并断开环。由于旋转 n 次(n 为链表长度)后链表会回到初始状态,因此我们只需计算 k % n 得到有效旋转次数,再从原头节点向后移动 n - (k % n) 个节点,该节点的下一个节点即为新头节点,断开此处的环即可完成旋转。

代码实现

cpp 复制代码
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 *	ListNode(int x) : val(x), next(nullptr) {}
 * };
 */
class Solution {
public:
    ListNode* rotateLinkedList(ListNode* head, int k) {
        // 边界情况:空链表或单节点链表无需旋转,直接返回原头节点
        if(head == nullptr || head->next == nullptr)
            return head;

        ListNode* cur = head;
        int size = 1;
        // 遍历链表统计长度,同时定位到尾节点
        while(cur->next != nullptr)
        {
            size++;
            cur = cur->next;
        }
        // 将尾节点指向头节点,构建环形链表
        cur -> next = head; 

        // 计算有效旋转次数,避免重复旋转
        k %= size;
        // 计算需要移动的步数,找到新头节点的前驱节点
        int move_step = size - k;
        ListNode* mark = head;
        // 移动到新头节点的前驱节点
        for(int i = 1; i < move_step; i++)
            mark = mark->next;
        // 确定新头节点并断开环形结构
        ListNode* newhead = mark->next;
        mark->next = nullptr;
        return newhead;
    }
};

实现细节与实战思考

在实际解题过程中,边界条件的处理是避免出错的关键:空链表或仅有一个节点的链表,无论旋转多少次结果都不变,可直接返回原头节点。构建环形链表时,需确保遍历到真正的尾节点------即 cur->nextnullptr 的节点,而非仅遍历到最后一个有值节点。

关于步数计算的细节容易成为易错点:move_step 表示从原头节点到新头节点前驱节点的步数,循环从 1 开始而非 0,是因为初始时 mark 已指向原头节点(对应第1个节点),只需再移动 move_step - 1 次即可到达目标位置。例如链表长度为5、k=2 时,move_step=3,循环执行2次,mark 最终指向第3个节点,其下一个节点即为新头节点。

题目2 广度优先遍历打印二叉树问题

题目链接:牛客 JZ78 把二叉树打印成多行 [ 并不是原题但是比这道题简单,并不用分层,直接就是程序便利输入到一个数组中即可。

传送门:这道题和我今天做的每日一题很像,都是BFS解决二叉树层序遍历的问题,更加详细的解释可以看这篇博客 N 叉树的层序遍历

题目描述:

题目背景与核心需求

二叉树的层序遍历是广度优先搜索(BFS)的经典应用,本题要求将二叉树按层打印,每一层的节点值单独存入一个数组,最终返回二维数组结果。不同于普通的层序遍历,本题需要精准区分每一层的节点,避免不同层的节点混在一起,这就要求在遍历过程中对每一层的节点数量进行精准统计。

算法分析

广度优先遍历依赖队列实现,核心思路是利用队列的"先进先出"特性,逐层处理节点。在遍历开始时,先将根节点入队;每一轮循环中,先记录当前队列的大小(即当前层的节点数),再依次取出该数量的节点,将节点值存入当前层的数组,同时将每个节点的左右子节点(若存在)入队。当当前层的所有节点处理完毕后,将该层的数组存入结果集,重复此过程直到队列为空。这种方式能确保每一轮循环仅处理一层节点,天然实现了层与层的分隔。

代码实现

cpp 复制代码
/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 *	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
#include <queue>
#include <vector>
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        // 存储最终的分层打印结果
        vector<vector<int>> ret;
        // 边界条件:空树直接返回空结果
        if(pRoot == nullptr) return ret;

        // 队列用于存储待处理的节点,实现广度优先遍历
        queue<TreeNode*> q;
        q.push(pRoot);
        
        // 队列非空时,说明还有未处理的节点
        while(!q.empty())
        {
            // 记录当前层的节点数量,这是分层的关键
            int level_size = q.size();
            // 存储当前层的节点值
            vector<int> level_nums;
            // 遍历当前层的所有节点
            while(level_size--)
            {
                TreeNode* tmp = q.front();
                level_nums.push_back(tmp->val);
                q.pop();
                // 左子节点存在则入队,为下一层遍历做准备
                if(tmp->left != nullptr)    q.push(tmp->left);
                // 右子节点存在则入队
                if(tmp->right != nullptr)    q.push(tmp->right);
            }
            // 将当前层的结果存入最终结果集
            ret.push_back(level_nums);
        } 
        return ret;
    }
};

细节分析与实战思考

队列的使用是本题的核心,其"先进先出"的特性恰好匹配层序遍历"从上到下、从左到右"的顺序。在实际解题时,level_size 的取值时机尤为重要------必须在处理当前层节点前获取队列大小,因为处理节点的过程中会将下一层节点入队,若在循环中获取会导致统计的节点数包含下一层内容。

例如对于一棵三层二叉树,初始时队列仅含根节点,level_size=1,处理完根节点后,其左右子节点入队,此时队列大小变为2;第二轮循环中 level_size=2,处理完这两个节点后,它们的子节点入队,队列大小变为4,以此类推。这种方式能精准划分每一层的节点,避免层序混乱。

此外,边界条件的处理不可忽视:空树的情况下直接返回空的二维数组,避免后续操作中访问空指针导致程序崩溃。在遍历节点时,需先判断左右子节点是否存在,再将其入队,这是防止无效节点入队的必要步骤。

总结

  1. 旋转链表问题的核心是通过构建环形链表简化旋转操作,结合取模运算优化旋转次数,同时需精准定位新头节点的前驱节点以断开环形结构,边界条件(空链表、单节点链表)的处理是避免错误的关键。
  2. 二叉树分层打印的核心是利用队列实现广度优先遍历,通过记录每一轮循环前的队列大小区分不同层节点,确保每一轮仅处理当前层节点,子节点入队的时机和顺序决定了遍历的正确性。
  3. 两道题目均体现了"先优化问题模型,再处理细节"的解题思路:旋转链表将多次旋转转化为环形结构的一次断开,二叉树遍历将"分层"需求转化为队列大小的统计,这种思路能有效降低时间复杂度,提升代码效率。
相关推荐
Swift社区17 小时前
ArkTS Web 组件里,如何通过 javaScriptProxy 让 JS 同步调用原生方法
开发语言·前端·javascript
咔咔咔的17 小时前
1970. 你能穿过矩阵的最后一天
c++
秃了也弱了。17 小时前
FASTJSON库:阿里出品java界json解析库,使用与踩坑记录
java·开发语言·json
_OP_CHEN17 小时前
【从零开始的Qt开发指南】(十九)Qt 文件操作:从 I/O 设备到文件信息,一站式掌握跨平台文件处理
开发语言·c++·qt·前端开发·文件操作·gui开发·qt文件
CSDN_RTKLIB17 小时前
【std::map】双向迭代器说明
c++·stl
王老师青少年编程18 小时前
信奥赛C++提高组csp-s之欧拉回路
c++·算法·csp·欧拉回路·信奥赛·csp-s·提高组
No0d1es18 小时前
2025年12月 GESP CCF编程能力等级认证C++六级真题
c++·青少年编程·gesp·ccf·6级
Terrence Shen18 小时前
【CUDA编程系列】之01
c++·人工智能·深度学习·机器学习
墨有66618 小时前
数学分析栈的出栈顺序:从算法判断到数学本质(卡特兰数初探)
c++·算法·数学建模