面经总结(初战)

今天进行了一场面试,岗位是字节跳动后端开发,发现对跨考真难,剑指才刷了三分之一,动态规划题目不会做,害,最终结果没通过,也是第一次面试,自己各个方面都存在问题。

且先当一次查漏补缺吧

计算机网络相关:TCP如何确保信息收到?

当时没记起来,应该是超时重发,发过一次数据包后,待服务器端确认无误,发送ACK,客户端收到后,再发下一个包,每发一个包,客户端就会进行计时,超出时间会进行重发

1. 校验和:发送的数据包的二进制相加然后取反,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。 

2. 确认应答+序列号(累计确认+seq):接收方收到报文就会确认(累积确认:对所有按序接收的数据的确认)。TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 

4. 超时重传:当TCP发出一个段后,它启动一个定时器等待目的端确认收到这个报文段如果不能及时收到一个确认,将重发这个报文段。 

3. 流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能告知发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。接收方有即时窗口(滑动窗口),随ACK报文发送。如果接收到窗口大小的值为0,那么发送方将停止发送数据。并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端。 

4.拥塞控制:如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

操作系统:如何区分内核态和用户态?

CPU状态:

内核态(Kernel Mode):运行操作系统程序,操作硬件

用户态(User Mode):运行用户程序

特权环:R0、R1、R2和R3

R0相当于内核态,R3相当于用户态;

不同级别能够运行不同的指令集合;

用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令)

内核态--->用户态:设置程序状态字PSW

区别:

  • 内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;

  • 当程序运行在0级特权级上时,就可以称之为运行在内核态。

  • 运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。

  • 这两种状态的主要差别是

    处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的

    处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。

状态切换的发生方式

  • 系统调用

这是用户态进程主动要求切换到内核态的一种方式用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如前例中fork()实际上就是执行了一个创建新进程的系统调用。

而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。

用户程序通常调用库函数,由库函数再调用系统调用,因此有的库函数会使用户程序进入内核态(只要库函数中某处调用了系统调用),有的则不会。

  • 异常

当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

  • 外围设备的中断

当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,

如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

面试官还想问文件系统相关问题,我好像没学过,太难了,回头认真复习一波,加油!

在线编程这块主要是出了两个问题:

1、以数组序号为天数,值为股票价格,如何买卖,利润最大,输出买入天数,和卖出天数,以及利润。

这个题目倒是做出来了

2、求数列最大子数列和问题,要求返回最大子数列起止和最大和。

问题描述:一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和,求所有子数组的和的最大值。注意:当全是负数的情况时,返回最大的那个负数

  这个问题最开始想的是暴力破解,跟面试官交流后,发现不能使用暴力破解法,这个问题是一个动态规划问题。

从左到右扫描数组,在扫描过程中,记录数组的负数的个数和扫描过中数据中的最大值,并累加每个扫描到的数据的和,假设用变量thisSum(初值为0)保存,如果当前的累加值大于之前的累加值的最大值 (例如用变量sum记录,初值为0),则把当前的最大值保存为最大值(sum = thisSum),如果thisSum小于0,则把thisSum设置为0并重新进行累加。一直这样扫描数组,直到把数组扫描完。由于thisSum已经小于0,也就是说之前统计的和可以舍弃,因为把当前的元素累加之后,结果反而小了。例如把数组分成三部分AiB,因为A的值大于0,A+i的值小于0,所以如果从B开始从新累加,则其值一定比包括i然后去累加B的结果大,因为i小于0,而B中的和却不一定比在A之前累加的和大。由于如果数组全是负数时,要返回最大的负数,而从上面所说的说法中,我们可以看到当前累加总和(thisSum)总是与0进行比较,如果小于0则把thisSum置为0,所以当数组全是负数时,thisSum和数组的最大子序列之和(sum)总是为0,而与现实有点不一样,所以就要记录负数的数量,当负数的数量等于元素的个数(即全是负数)时,就要把最大连续子序列和置为最大的负数。这也是前面所说的,在扫描过程中记录负数的个数和最大元素的作用。

Code:

int MaxSum(int* a,int n)
{
    int sum = 0; //用于记录最大的连续子数组和
    int flag = 0;//用于记录负数的个数
    int MaxNum = *a;//用于记录数组中最大的数
    int ThisSum = 0;//用于记录当前的连续子数组和
    for(int i = 0; i < n; ++i)
    {
        if(a[i] < 0) //如果无素为负数,则把flag的值加1
            ++flag;
        if(MaxNum < a[i]) //记录数组当前的最大值
            MaxNum = a[i];
        ThisSum += a[i]; //累加更新当前的子数组之和
        if(ThisSum > sum)
        {
            //若当前连续子数组之和大于记录的子数组之和
            //则设置最大连续子数组之和为当前的和
            sum = ThisSum;
        }
        else if(ThisSum < 0)
        {
            //如果当前连续子数组之和小于0,则抛弃之前的连续子数组,
            //从此元素的下一个元素重新计算连续子数组之和
            ThisSum = 0;
        }
    }
    //若全是负数,最大值为数组中的最大无素
    if(flag == n)
        sum = MaxNum;
    return sum;
}

  该算法的时间复杂度只为O(N),而且常数为1,即只需要扫描一次数组即可完成任务。而且用到的辅助空间也非常少,只有四个变量,空间复杂度为O(1)。

今天总结一波:

  面试准备的都没考,基础还是不牢固,剑指还得刷,自己刷的题目太少了,另外操作系统和计算机网络的知识也有很多遗忘。

   选择后端开发,也是目前没办法的事,cv和推荐系统感觉要学的太多了,而且很多时候导师也不会帮忙,话说导师的研究方向跟cv方向还是有点差距。自己并不是很感兴趣。

前路漫漫,吾将上下而求索。

猜你喜欢

转载自www.cnblogs.com/lvpengbo/p/12592387.html