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;
}
- 宏定义的变量在引用的时候,用 ()括起来,防止预处理器展开的错误。
- (
a > b ? action1 : action2 )
这样的方式和if —else
结果一样,但他会使得编译器产生更优化的代码,这在嵌入式编程中比较重要。 - 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
,将返回++i
,i
就被加了两次,这当然是错误的。
2.虚拟内存跟物理内存的关系?内存映射如何实现?
虚拟内存提供了三个重要的能力: 缓存,内存管理,内存保护
-
将主存视为一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据
-
为每个进程提供了一致的地址空间,简化内存管理
-
保护了每个进程的地址空间不被其他进程破坏
https://blog.csdn.net/lvyibin890/article/details/82217193
3.进程的内存布局?
进程的内存布局在结构上是有规律的,对于 linux 系统上的进程,其内存空间一般可以粗略地分为以下几大段,从高内存到低内存排列:
-
内核态内存空间
,其大小一般比较固定(可以编译时调整),但 32 位系统和 64 位系统的值不一样。 -
用户态的
栈
,大小不固定,可以用ulimit -s 进行调整,默认一般为 8M,从高地址向低地址增长。 -
mmap区域(内存
映射段
),既可以从高地址到低地址延伸(所谓 flexible layout),也可以从低到高延伸(所谓 legacy layout),看进程具体情况。 -
brk 区域(
堆
),紧邻数据段(甚至贴着),从低位向高位伸展,但它的大小主要取决于 mmap 如何增长,一般来说,即使是 32 位的进程以传统方式延伸,也有差不多 1 GB 的空间。 -
数据段
,主要是进程里初始化和未初始化(BSS)的全局数据总和,当然还有编译器生成一些辅助数据结构等等),大小取决于具体进程,其位置紧贴着代码段。 -
代码段
,主要是进程的指令,包括用户代码和编译器生成的辅助代码,其大小取决于具体程序,但起始位置根据 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升水?
- 8升杯倒进7升杯,余1升,倒进7升杯;
- 8升杯倒进7升杯,余2升,倒进7升杯;
- 8升杯倒进7升杯,余3升,倒进7升杯;
- 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;
}
};