当前位置: 首页 > news >正文

2023秋招面试准备

2022 秋招资料

算法数据结构

螺旋矩阵问题

螺旋矩阵I(将 1~n*m 个数按蛇形方向填入数组中)

记录蛇形矩阵偏移量方法,四个不同方向右下左上,判断什么情况下矩阵遍历完,1.要么出界 2.要么走完

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int n = matrix.size(), m = matrix[0].size();
        int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
        vector<int> res;
        vector<vector<bool>> vis(n, vector<bool>(m));
        for (int i = 0, x = 0, y = 0, d = 0; i < n * m; i ++) {
            res.push_back(matrix[x][y]);
            vis[x][y] = true;
            int nx = x + dir[d][0], ny = y + dir[d][1];
            if (nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny]) {
                d = (d + 1) % 4;
                nx = x + dir[d][0], ny = y + dir[d][1];
            }
            x = nx, y = ny;
        }

        return res;
    }
};

螺旋矩阵 II

desc: 给定 n 生成一个包含一到 n^2的矩阵, 按照螺旋矩阵的方式存储

解法:和螺旋矩阵 I 类似,通过偏移量构造

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
       for (int x = 0, y = 0, cnt = 1, d = 0; cnt <= n * n; cnt ++) {
           res[x][y] = cnt;
           int nx = x + dir[d][0], ny = y + dir[d][1];
           if (nx < 0 || nx >= n || ny < 0 || ny >= n || res[nx][ny]) {
               d = (d + 1) % 4;
               nx = x + dir[d][0], ny = y + dir[d][1];
           }
           x = nx, y = ny;
       } 
        return res;
    }
};

螺旋矩阵 III

desc:给定一个nxm的矩阵,和一个开始走的起点坐标,求螺旋方向走的坐标,并且可以出界, 如下图所示

class Solution {
public:
    vector<vector<int>> spiralMatrixIII(int n, int m, int x, int y) {
        vector<vector<int>> res;
        int tot = n * m;
        res.push_back({x, y});
        int d = 0;
        int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
        // k 循环表示模拟圆环的半径, 有一个规律1, 1, 2, 2, 3, 3...
        for (int k = 1; res.size() < tot; k ++) {
            // i 循环表示矩阵是按照每个半径会走两次
            for (int i = 0; i < 2 && res.size() < tot; i ++) {
                // j循环表示开始走半径为 k 的元素
                for (int j = 0; j < k && res.size() < tot; j ++) {
                        int nx = x + dir[d][0], ny = y + dir[d][1];
                        // 在矩阵内即可添加进去
                        if (nx >= 0 && nx < n && ny >= 0 && ny < m)
                            res.push_back({nx, ny});
                        x = nx, y = ny;
                }
                d = (d + 1) % 4;
            }
        }
        return res;
    }
};

螺旋矩阵 IV

desc: 给定链表从链表中构建一个蛇形矩阵,和 I、II 的构建方式一样

class Solution {
public:
    vector<vector<int>> spiralMatrix(int n, int m, ListNode* head) {
        vector<vector<int>> res(n, vector<int>(m, -1));
        int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
        int x = 0, y = 0, d = 0;
        for (auto p = head; p != nullptr; p=p->next) {
            res[x][y] = p->val;
            int nx = x + dir[d][0], ny = y + dir[d][1];
            if (nx < 0 || nx >= n || ny < 0 || ny >= m || res[nx][ny] != -1) {
                d = (d + 1) % 4;
                nx = x + dir[d][0], ny = y + dir[d][1];
            }
            x = nx, y = ny;
        }
        return res;
    }
};

单链表排序问题

单链表快速排序(时间O(nlogn), 空间(O(logn)))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oay7AL1b-1662036839548)(/Users/liyan/Library/Application Support/typora-user-images/image-20220831102437989.png)]

链表快排基本思路

  • 选定链表头基准元素
  • 建立三个子链表left,mid,right
    • left存储比基准元素小的节点
    • mid 存储和基准元素相等的节点
    • right 存储比基准元素大的节点
  • 递归的去排序第二个过程
    • 递归出口节点空或者链表只有一个节点
  • 最后将 left 拼接 mid 拼接 right
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* getTail(ListNode* head) {
        while (head->next) head=head->next;
        return head;
    }
    ListNode* sortList(ListNode* head) {
        if (!head || !head->next) return head;

        auto left = new ListNode(-1), mid = new ListNode(-1), right = new ListNode(-1);
        auto ltail = left, rtail = right, mtail = mid;
        // int cnt = 0;
        // int val = head->val;
        // for (auto p = head; p; p = p->next) {
        //     cnt ++;
        // }
        // int n = cnt;
        // for (auto p = head; p; p = p->next) {
        //     cnt --;
        //     if (cnt == n / 2) {
        //         val = p->val;
        //         break;
        //     }
            
        // }
        int val = head->val;
        for (auto p = head; p; p=p->next) {
            if (p->val < val) ltail = ltail->next = p;
            else if (p->val == val) mtail = mtail->next = p;
            else rtail = rtail->next = p;
        }
        ltail->next = mtail->next = rtail->next = nullptr;
        left->next = sortList(left->next);
        right->next = sortList(right->next);

        getTail(left)->next = mid->next;
        getTail(left)->next = right->next;
        auto p = left->next;
        delete left;
        delete mid;
        delete right;
        return p;
    }
};

时间复杂度O(nlogn)、空间复杂度O(logn), 疑问:为什么过不了leetcode 原题

可以将基准值随机选取而不是选取头尾节点

单链表排序(归并)(时间:O(nlogn) 空间:常数,迭代)

迭代写法

想法:

  • 先求出要排序的链表结点数 n
  • i 循环控制排序每一个子区间,子区间长度为 1,2,4,8…2^n
  • 定义一个 dummy 节点为初始节点
  • j循环控制合并每个子区间, j + i <= n, j += 2 * i;
  • p 指向合并子区间第一个节点,q 指向合并子区间第二个节点, 通过 while 循环控制
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        int n = 0; // 记录链表节点个数
        for (auto p = head; p; p = p->next) n ++; 
        auto dummy = new ListNode(-1); // 虚拟头结点,指向排序好的第一个节点
        dummy->next = head; 

        for (int i = 1; i < n; i *= 2) { // 循环 log 层, 小于n是因为等于n时说明所有元素均归并完毕,大于n时同理
            auto cur = dummy; // cur表示准备排序子区间长度为 i 的起始位置
            for (int j = 1; j + i <= n; j += 2 * i) { // j代表每一段的开始,每次将两段有序段归并为一个大的有序段,故而每次+2i
                auto p = cur->next, q = p; // p 是第一段起始位置, q是第二段起始位置
                for (int k = 0; k < i; k ++) q = q->next; 
                int l = 0, r = 0; 
                while (l < i && r < i && p && q) {
                    if (p->val <= q->val) cur = cur->next=p, p = p->next, l ++;
                    else cur = cur->next=q, q = q->next, r ++;
                }
                while (l < i && p) cur = cur->next = p, p = p->next, l ++;
                while (r < i && q) cur = cur->next = q, q = q->next, r ++;
                cur->next = q;
            }
        }
        return dummy->next;
    }
};
递归写法O(nlogn) + 空间O(logn)

通过递归实现链表归并排序,有以下两个环节:

  • 分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);

    • 我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
    • 找到中点 slow 后,执行 slow.next = None 将链表切断。
    • 递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
    • cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
  • 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。

    • 双指针法合并,建立辅助ListNode h 作为头部。
    • 设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
    • 返回辅助ListNode h 作为头部的下个节点 h.next。
    • 时间复杂度 O(l + r),l, r 分别代表两个链表长度。
    ListNode* merge(ListNode* l, ListNode* r) {
            ListNode *dummy = new ListNode(-1);
            auto cur = dummy;
            while (l && r) {
                if (l->val <= r->val) cur = cur->next = l, l = l->next;
                else cur = cur->next = r, r=r->next;
            }
            cur->next = (l ? l : r);
            return dummy->next;
        }
        ListNode* sortList(ListNode* head) {
            if (!head || !head->next) return head;
    
            ListNode* fast = head, *slow = head; // 快慢指针找到中间的 cut 节点分为左右两段
            while (fast->next && fast->next->next) {
                fast = fast->next->next;
                slow = slow->next;
            }
            fast = slow;
            slow = slow->next; // cut 的右边的首个节点
            fast->next = nullptr; // fast 是 cut 后的左边的最后一个节点
    
            ListNode* l = sortList(head), *r = sortList(slow);
            return merge(l, r); // 合并
        }
    

单链表排序插入

插入排序 算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

为了方便处理边界情况,我们建立虚拟头结点,指向原链表头部。
然后扫描原链表,对于每个节点 vv,从前往后扫描结果链表,找到第一个比 vv 大的节点 uu,将 vv 插入到 uu 之前。

时间复杂度分析:一共遍历 nn 个节点,对于每个节点找合适位置时,最多需要遍历 O(n)O(n) 次,所以总时间复杂度是 O(n2)O(n2)。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        ListNode* dummy = new ListNode(-1);
        while (head) { // head 指向当前待插入的节点
            ListNode* next = head->next;   // next指向下一个循环待插入的节点
            ListNode* p = dummy; // dummy存的是待插入的链表
            while (p->next && p->next->val <= head->val) p = p->next; //找到插入 head 的位置 p

            head->next = p->next; // 将head 插入 p后面
            p->next = head; // 

            head = next; // head更新为下一个循环要插入的节点

        }
        return dummy->next;

    }
};

单链表排序选择

选择排序过程

单链表排序冒泡

单链表排序堆排序

C++语言特性

C++ 中什么时候把析构函数写为虚函数 ?

当一个类为基类,通常其析构函数被声明为虚函数

why ?

因为如果定义基类指针,根据赋值兼容性问题,基类指针可以指向动态生成子类对象的地址,此时子类对象已经被充当基类使用

而在 delete 基类对象时,如果不定义析构函数为虚函数,则不会调用派生类的析构函数,这样就造成内存泄漏了.

C++中可以把构造函数声明为虚函数 ?

不可以。因为虚函数的调用依靠于虚函数表。然而在构造函数执行完时,虚函数表指针才正确初始化

构造函数不可以声明为虚函数,那么可以实现多态 ?

不可以,因为析构函数一旦被调用,虚函数表指针就会被销毁。在构造函数中实现体重也不可能发生多态行为,因为在构造函数执行时,虚函数表指针还没被正确初始化。

计算机网络

Web Server 项目

数据库

操作系统

相关文章:

  • 【学习笔记】(数学)线性代数-矩阵的概念和特殊矩阵
  • 用ARM进行汇编语言编程(2)算数指令,CPSR寄存器与逻辑运算
  • 计算机毕业设计ssm趣评美食管理评论系统lrt3w系统+程序+源码+lw+远程部署
  • 《JavaScript从入门到精通》|变量作用域|垃圾回收|闭包【函数进阶篇】
  • 工程项目管理概述
  • 【Q-Learning】TD算法的一种
  • 【QT】Qt调用OCX控件详解
  • 设立“丰收杯”建设吨粮田 国稻种芯-株洲:破解种粮世界性难题
  • Linux内核设计与实现 第一章 Linux内核简介
  • dubbo java api
  • switch选择结构
  • Vue基础(九)——ElementUI
  • Linux(一)最简单的LED驱动程序(应用层和驱动层分析)
  • 猿创征文 | [云原生]为微服务保驾护航之链路跟踪skywalking保姆级搭建教程
  • 雅思口语高分课程
  • @jsonView过滤属性
  • 《Java编程思想》读书笔记-对象导论
  • ECMAScript入门(七)--Module语法
  • es6(二):字符串的扩展
  • JAVA之继承和多态
  • Linux CTF 逆向入门
  • node.js
  • npx命令介绍
  • PhantomJS 安装
  • Rancher如何对接Ceph-RBD块存储
  • Shell编程
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • unity如何实现一个固定宽度的orthagraphic相机
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 好的网址,关于.net 4.0 ,vs 2010
  • 猴子数据域名防封接口降低小说被封的风险
  • 开源地图数据可视化库——mapnik
  • 利用DataURL技术在网页上显示图片
  • 聊聊redis的数据结构的应用
  • 学习ES6 变量的解构赋值
  • 优秀架构师必须掌握的架构思维
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • ​TypeScript都不会用,也敢说会前端?
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (zhuan) 一些RL的文献(及笔记)
  • (八十八)VFL语言初步 - 实现布局
  • (二)WCF的Binding模型
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • ..回顾17,展望18
  • .NET NPOI导出Excel详解
  • .NET 常见的偏门问题
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • @EnableConfigurationProperties注解使用