每日面经(二十二)

1.高并发场景下有哪些注意事项?

在高并发场景下,为了保证应用的稳定性和性能,需要注意以下几点:

  1. 数据库优化: 在高并发场景下,数据库经常会成为系统性能的瓶颈。因此,可以采用数据库读写分离、查询语句优化、索引优化等手段来提升数据库的性能。

  2. 缓存优化: 缓存是提升系统性能的重要手段,可以采用多级缓存、缓存预热、缓存穿透、缓存雪崩等策略来提升缓存的效率。

  3. 代码优化: 在高并发场景下,代码的性能也是至关重要的。可以通过尽可能减少HTTP请求、选择高效的算法、使用异步编程等方式来提升代码性能。

  4. 限流熔断: 在高并发场景下,限流熔断机制可以帮助我们对系统的请求进行平滑处理,防止系统压力过大导致系统宕机,可以采用令牌桶算法、漏桶算法、断路器等方案。

  5. 分布式部署: 分布式架构可以通过分布式部署和负载均衡等手段来处理请求,充分利用系统的资源,提高系统的性能和稳定性。

2.socket编程的流程

Socket 编程流程包括以下步骤:

  1. 创建 Socket:使用 socket() 函数创建一个套接字,该套接字可作为客户端或服务器端使用。

  2. 绑定 Socket:将套接字与连接到的 IP 地址和端口进行绑定,使用 bind() 函数。

  3. 监听连接请求:在服务器端,调用 listen() 函数监听客户端的请求。

  4. 接受连接:使用 accept() 函数接受客户端连接的请求,该函数一直等到有客户端连接请求时才返回。

  5. 接收数据:使用 recv() 函数从套接字中读取数据,或用 read() 函数等价地读取数据。

  6. 发送数据:使用 send() 函数将数据发送到连接的另一端,也可以使用 write() 函数等价地发送数据。

  7. 关闭连接:使用 close() 函数关闭连接。在服务器端,可以使用 shutdown() 函数关闭特定的输入、输出或输入/输出通道,从而实现半关闭状态。

  8. 结束 Socket:使用 close() 函数关闭 Socket。

3.C++中的map和unordered_map的区别和使用场景

C++中的map和unordered_map都是用来存储键值对的容器,但它们之间有一些区别,下面分别介绍。

        1.内部实现

map底层是红黑树,可以保证数据有序,查找复杂度是O(log(n));而unordered_map底层是哈希表,数据是无序的,但在大部分情况下查找复杂度是O(1)。

        2.插入和删除

map进行插入和删除时,由于要维护数据的有序性,需要做大量的平衡调整,所以插入和删除的操作效率相对较低,时间复杂度是O(log(n))。而unordered_map插入和删除时只需要进行简单的哈希计算,复杂度是O(1)。

        3.迭代器失效

由于map是平衡二叉树,任何一个节点的插入和删除都可能导致树的结构改变,因此在对map进行插入和删除操作时,迭代器有可能会失效。而unordered_map的插入和删除操作不会导致迭代器失效。

        4.使用场景

map适合需要按照键的大小进行排序的场景,例如统计英文单词出现的次数;而unordered_map适合对数据进行快速的查询和插入的场景,例如实现字典或者图的邻接表。

4.合并N个有序数组,要求时间复杂度尽可能低

要合并N个有序数组,最直观的想法是把所有数组都合并成一个大数组,然后再排序。但是这样做的时间复杂度为O(N* Klog(N K)),其中N为数组个数,K为每个数组的平均长度,即每次合并时需要对所有元素进行排序,非常耗时。

一种更加高效的算法是使用类似于归并排序的方法,将N个有序数组逐个合并。我们可以将这N个数组分成两组,先将这两组分别合并成一个大数组,然后再将这两个大数组合并成一个更大的数组。每次合并后,数组的个数减半,这样重复执行,直到只剩下一个有序数组即可。

假设总的元素个数为M,那么这样做的时间复杂度为O(M*log(N))。具体实现可以使用优先队列(即堆)来维护各个数组元素的大小关系,每次取出堆中最小的元素,将其所在数组的下一个元素入堆,直到堆为空。

以下是示例代码实现:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<int> merge_sorted_arrays(vector<vector<int>>& arrays) {
    // 使用堆来维护所有数组的大小关系
    auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) {
        return a.first > b.first;
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
    // 将所有数组的第一个元素入堆
    for (int i = 0; i < arrays.size(); ++i) {
        if (!arrays[i].empty()) {
            pq.push(make_pair(arrays[i][0], i));
        }
    }
    vector<int> result;
    while (!pq.empty()) {
        auto top = pq.top();
        pq.pop();
        result.push_back(top.first);
        int i = top.second;
        if (++arrays[i].begin() != arrays[i].end()) {
            pq.push(make_pair(arrays[i][1], i));
        }
    }
    return result;
}

int main() {
    vector<vector<int>> arrays = {
        {1, 3, 5, 7},
        {2, 4, 6},
        {0, 8, 9, 10}
    };
    auto result = merge_sorted_arrays(arrays);
    for (auto num : result) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

上面的代码中,我们先将所有数组的第一个元素入堆,然后从堆中取出最小的元素,将其所在的数组的下一个元素入堆。若该数组已经合并完,即其所有元素都已经处理完毕,则忽略该数组。最终得到的result即为合并后的有序数组。

该算法的时间复杂度为O(M*log(N)),其中M是所有数组元素的个数,N为数组个数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值