2021-04-14 字节客户端二面

1.实现宏运算:返回两个值中的最大值

#include <stdio.h>

#define MIX(X,Y) ({\
    typeof(X) x_ = (X);\
    typeof(Y) y_ = (Y);\
    (x_< y_)? x_:y_;\
}) 

#define MAX(X,Y) ({\
    typeof(X) x_ = (X);\
    typeof(Y) y_ = (Y);\
    (x_>y_)? x_:y_;\
})

int main(){
	int num1=0, num2=0;
	printf("mix is %d,max is %d\n", MIX(num1++, num2), MAX(++num1, num2));
	return 0;
}
  1. 宏定义的变量在引用的时候,用 ()括起来,防止预处理器展开的错误。
  2. a > b ? action1 : action2 ) 这样的方式和 if —else 结果一样,但他会使得编译器产生更优化的代码,这在嵌入式编程中比较重要。
  3. typeof 关键字——获取变量类型。

需要注意的是
1 . typeof 关键字 用于获得变量的数据类型 。
2 . 宏定义的实现,用 { } 作为宏整体,里面是一个代码块,语句用 ; 隔开 。
3 . 当宏的实现长度很长的时候,使用换行符 \ 换到下一行 。
4 . 使用输入数据的类型定义局部变量 x_ 和 y_ 实现对原始数据的保护。
5 . 宏实现,不能用 ; 结尾

不能写成#define MAX(a,b) ((a)>(b)?(a):(b)),存在的问题是:例如发起调用MAX(++i,0),会变成((++i)>(0)?(++i):(0)),如果(++i)>0,将返回++ii就被加了两次,这当然是错误的。


2.虚拟内存跟物理内存的关系?内存映射如何实现?

虚拟内存提供了三个重要的能力: 缓存,内存管理,内存保护

  1. 将主存视为一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据

  2. 为每个进程提供了一致的地址空间,简化内存管理

  3. 保护了每个进程的地址空间不被其他进程破坏

在这里插入图片描述

https://blog.csdn.net/lvyibin890/article/details/82217193


3.进程的内存布局?

在这里插入图片描述
进程的内存布局在结构上是有规律的,对于 linux 系统上的进程,其内存空间一般可以粗略地分为以下几大段,从高内存到低内存排列:

  1. 内核态内存空间,其大小一般比较固定(可以编译时调整),但 32 位系统和 64 位系统的值不一样。

  2. 用户态的,大小不固定,可以用ulimit -s 进行调整,默认一般为 8M,从高地址向低地址增长。

  3. mmap区域(内存映射段),既可以从高地址到低地址延伸(所谓 flexible layout),也可以从低到高延伸(所谓 legacy layout),看进程具体情况。

  4. brk 区域(),紧邻数据段(甚至贴着),从低位向高位伸展,但它的大小主要取决于 mmap 如何增长,一般来说,即使是 32 位的进程以传统方式延伸,也有差不多 1 GB 的空间。

  5. 数据段,主要是进程里初始化和未初始化(BSS)的全局数据总和,当然还有编译器生成一些辅助数据结构等等),大小取决于具体进程,其位置紧贴着代码段。

  6. 代码段,主要是进程的指令,包括用户代码和编译器生成的辅助代码,其大小取决于具体程序,但起始位置根据 32 位还是 64 位一般固定(-fPIC, -fPIE等除外)。

以上各段(除了代码段数据段)其起始位置根据系统是否起用 randomize_va_space 一般稍有变化,各段之间因此可能有随机大小的间隔。

https://blog.csdn.net/cztqwan/article/details/80248479


4.全局变量和静态变量存储在哪里?他们的区别是什么?

初始化的放在数据区,未初始化的放在BSS区。

区别在于:作用域不同,静态变量的作用域仅限于本文件。


5.TCP三次握手为什么不可以两次?四次挥手为什么不可以三次?

TCP为什么要三次握手,tcp为什么可靠?

为什么不能两次握手:(防止已失效的连接请求又传送到服务器端,因而产生错误)

假设改为两次握手,client端发送的一个连接请求在服务器滞留了,这个连接请求是无效的,client已经是closed的状态了,而服务器认为client想要建立一个新的连接,于是向client发送确认报文段,而client端是closed状态,无论收到什么报文都会丢弃。而如果是两次握手的话,此时就已经建立连接了。服务器此时会一直等到client端发来数据,这样就浪费掉很多server端的资源。

三次握手的最主要目的是保证连接是双工的

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

为什么需要TIME_WAIT?

TIMEWAIT状态也称为2MSL等待状态。

1)为实现TCP这种全双工(full-duplex)连接的可靠释放

这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

2)为使旧的数据包在网络因过期而消失

每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,我们也未必全部数据都发送给对方了,所以我们不可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,我们的ACK和FIN一般都会分开发送。

https://www.cnblogs.com/pengmn/p/10836784.html


6.给8升、7升容量的杯子,如何量出4升水?

  1. 8升杯倒进7升杯,余1升,倒进7升杯;
  2. 8升杯倒进7升杯,余2升,倒进7升杯;
  3. 8升杯倒进7升杯,余3升,倒进7升杯;
  4. 8升杯倒进7升杯,余4升,over!

7.如何在无序数组中找到第K小的元素?要求时间复杂度最小

方法一:使用大小为K的大根堆,时间复杂度为O(N*logK)

方法二:基于快排思想找出Top K,时间复杂度O(N*logN),具体实现可参照快排思想实现Top K


8.内存淘汰算法lru及其实现?

LRU Cache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

实现思路:

get(key):直接获取map元素,并将被访问元素置于栈顶
put(key):元素入栈,若栈满,删除栈底元素;若更新元素,将元素置于栈顶,并更新map对应value

双链表模拟栈的优势:链头模拟栈底,链尾模拟栈顶,剪接节点置于栈顶的操作只需要O(1)复杂度

代码:

class LRUCache {
private:
    int capasize_;
    list<pair<int,int>>cache;//双向链表
    unordered_map<int,list<pair<int,int>>::iterator> mp;//哈希表

public:
    //构造函数
    LRUCache(int capacity) {
        capasize_=capacity;
    }

    int get(int key) {
        const auto it=mp.find(key);
        //判断key是否存在哈希表中
        if(it==mp.cend())
            return -1;//不存在返回-1
        //如果存在则将双向链表中含关键字key的节点移至头部
        cache.splice(cache.begin(),cache,it->second);
        //返回value
        return it->second->second; 
    }

    void put(int key, int value) {
        const auto it=mp.find(key);
        //判断key是否存在哈希表中,存在则改变value,然后将双向链表中含关键字key的节点移至头部
        if(it!=mp.cend())
        {
            it->second->second=value;
            cache.splice(cache.begin(),cache,it->second);
            return;//记得return
        }
        //如果不存在则要判断此时双向链表容量是否已满
        //若满了则要去除双向链表中的尾节点以及哈希表中key关键字位置
        if(cache.size()==capasize_)
        {
            const auto&node=cache.back();
            mp.erase(node.first);
            cache.pop_back();
        }
        //新建一个节点将其插入到链表头部
        cache.emplace_front(key,value);
        //让哈希表中key关键字指向链表新的头节点
        mp[key]=cache.begin();
    }
};

9.给定数组,每个元素为当天的股价,允许两次买入、两次卖出,找出最大收益?

两次买卖的话拆分成5种状态:

dp[0] 没有进行买卖
dp[1] 第一次买入
dp[2] 第一次卖出
dp[3] 第二次买入
dp[4] 第二次卖出

我觉得问题的关键在于,第一次和第二买卖不会进行交叉,所以第一次买了之后只能卖了才进行下一次买卖,这样问题的状态拆分就很简单了。动态规划解法的话需要初始状态和状态公式。

初始状态:假设当天买或者不买,dp[0]显然是0,dp[2]和dp[4]显然还没卖出是没有收益的,dp[1]和dp[3]买入肯定是扣除当天的价格的。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        dp = [0 for _ in range(5)]
        dp[0] = 0
        dp[1] = -prices[0]
        dp[2] = float('-inf')
        dp[3] = -prices[0]
        dp[4] = float('-inf')
        for i in range(len(prices)):
            # 如果价格比昨天低就更新第一次买入的价格
            dp[1] = max(dp[1], dp[0]-prices[i])
            # 如果卖出价格比昨天卖出的价格高就更新
            dp[2] = max(dp[2], dp[1]+prices[i])
            # 如果今天买入
            dp[3] = max(dp[3], dp[2]-prices[i])
            dp[4] = max(dp[4], dp[3]+prices[i])
        return max(dp)

https://blog.csdn.net/qq_35697696/article/details/111304746


10.给定数组,滑动窗口大小设K,求每个滑动窗口的最大值?

使用递减的单调队列实现,每次取队头

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size()<k)
            return {};

        vector<int> res;
        deque<int> q;       //维持一个单调递减队列,队头元素最大,新元素从队尾插入

        for(int i=0;i<nums.size();i++){
            if(!q.empty() && q.front()==i-k)    //删除过期元素
                q.pop_front();
            
            //删除队列中比新元素小的元素,因为这些元素以后永远不会被用到
            while(!q.empty() && nums[i]>nums[q.back()]) 
                q.pop_back();
            
            q.push_back(i);     //将新元素插入到队尾
            if(i>=k-1) res.push_back(nums[q.front()]);
        }
        
        return res;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值