文件组合
将传输文件切分成多个部分,按照原排列顺序,每部分文件编号为一个正整数。
class Solution {
public:
vector<vector<int>> fileCombination(int target) {
vector<vector<int>> res;
int sum = 0;
for(int i=1; i<target;i++){
sum = i;
for(int j=i+1; j<target; j++){
sum += j;
if(sum == target){
vector<int> temp;
for(int k=i; k<=j; k++){
temp.push_back(k);
}
res.push_back(temp);
}else if(sum > target){
break;
}
}
}
return res;
}
};
早餐组合
求2~2000的素数个数,时间复杂度优于O(nlogn)
int countPrimes(int n){
if(n < 2){
return 0;
}
vector<bool> isPrime(n+1, true);
isPrime[0] = isPrime[1] = false;
for(int i=2; i*i <= n; i++){
if(isPrime[i]){
for(int j=i*i; j<=n; j+=i){
isPrime[j] = false;
}
}
}
int primeCount = 0;
for(int i=2; i<=n; i++){
if(isPrime[i]){
++count;
}
}
return primeCount;
}
isPrime数组:用一个布尔类型的数组isPrime来标记每个数是否是素数,初始化为true。
- 从2开始遍历到sqrt(n),因为一个合数的最小质数一定小于等于它的平方根。
- 如果i是素数,那么i的倍数一定不是素数,因此将i的倍数都标记为false。
- 这里优化的地方在于,对于一个合数j,它会被它的最小质因数筛掉,而不会被大的质因数重复筛选掉,避免了重复计算。
常用的IO多路复用方式
IO多路复用是一种高效的I/O处理机制,它允许单个进程同时监控多个文件描述符(如网络套接字),一旦某个描述符就绪(可读或可写),就能及时通知程序进行相应的读写操作。
大大提高了程序处理并发连接的能力,广泛应用于高性能网络服务器。
为什么需要IO多路复用:
- 传统阻塞I/O的弊端:每个连接都需要一个线程来处理,当连接数量增多时,线程开销过大,系统消耗资源严重。
- IO多路复用的优势:单个线程可以监控多个连接,有效地利用系统资源,提高并发性能。
select
将所有文件描述符放到一个数组中,轮训遍历这个数组,检查每个文件描述符的状态。
缺点:
- 单个进程能监视的文件描述符数量存在最大限制(通常为1024)。
- 需要频繁切换用户态和内核态,效率较低。
- 需要每次把整个描述符集合从用户态拷贝到内核态,开销较大。
poll
与select类似,也是通过轮询的方式检查文件描述符的状态。
- 使用链表来存储文件描述符,解决了描述符数量的限制。
- 其它方面与select类似,仍然存在效率问题。
epoll
epoll使用事件驱动的方式,只有当某个文件描述符的状态发生变化时,才会通知应用程序。
- 效率更高:epoll使用红黑树和回调机制,避免了每次都线性遍历全部连接。
- 支持大量文件描述符:理论上支持百万级别。
- 内核事件表:内核中维护一个事件表,将用户注册的事件添加到这个表中。
select
select是最早的一种I/O多路复用机制,在POSIX标准中被广泛支持。
select允许进程监视多个文件描述符,一旦这些描述符中的任何一个变为可读、可写或者有异常发生,select就会返回。
缺点是select的性能随着监控的文件描述符数量增加而下降,因为每次调用都需要对所有描述符进行遍历。
select 是最早的 IO 多路复用机制,出现在 UNIX 系统中。它允许程序监视多个文件描述符,以查看是否可以进行 IO 操作(如读、写或有异常发生)。
select接受三个文件描述符集合(可读、可写、异常),以及一个超时时间。
程序将这些集合传递给select,select监听这些描述符,一旦其中的某个或多个描述符就绪,select 就会返回,程序再对就绪的描述符进行相应的 IO 操作。
兼容性好,几乎在所有的 UNIX 系统上都可以使用。
使用简单,易于理解和实现。
POLL
poll与select类似,但没有文件描述符数量的限制,并且使用了更灵活的数据结构(链表),可以监视更多的文件描述符。
但是它仍然需要遍历所有的文件描述符,因此在处理大量描述符时,性能没有本质提升。
EPOLL
epoll是Lnux下专门为解决select和poll的性能问题而设计的,它们使用了事件通知机制,而不是遍历文件描述符。
epoll有两种工作模式:水平触发和边缘触发,后者能够高效地处理大量并发连接。
epoll非常适用于高并发服务器的开发。
首先通过epoll_create创建一个epoll实例,然后通过epoll_ctrl将需要监控的文件描述符添加到epoll实例中,最后调用epoll_wait等待就绪的事件。
Linux常用的信号
Linux系统中,信号是一种进程间通信机制,用于异步通知进程某个事件的发生。
当一个进程接收到信号时,它可以采取相应的动作,如终止、忽略或者执行特定的处理函数。
信号的作用:
- 进程控制:终止进程、暂停进程、继续进程等。
- 异常处理:处理程序错误、硬件故障等异常情况。
- 进程间通信:在进程之间传递信息。
常用信号:
- SIGINT:中断信号,通常由Ctrl+C产生,用于终止前台进程。
- SIGKILL:终止进程信号,无法被忽略或捕获。
- SIGALRAM:定时器信号,用于实现定时功能。
- SIGCHLD:子进程状态改变信号,当子进程终止或停止时,父进程会收到该信号。
- SIGHUP:终端挂起或控制进程终止。通常用于通知进程重新读取配置文件。
- SIGINT:中断信号,由Ctrl+C发送,用于终止前台进程。
- SIGQUIT:由Ctrl+\发送,通常用于生成核心转储并终止进程。
- SIGUSR1、SIGUSR2:由用户定义的信号,可以由用户和程序自行使用。
进程可以通过以下几种方式处理信号:
- 忽略信号:不做任何处理。
- 捕获信号:定义信号处理函数,当接收到信号时,执行该函数。
- 默认处理:系统默认的处理方式,通常是终止进程。
使用kill命令/函数向指定进程发送信号,硬件中断可以产生信号。
Linux系统中的七种主要文件类型
- 普通文件(-):最常见的文件类型,用于存储各种类型的数据,包括文本、图形、音频、视频等。
- 目录文件(d):用于组织其它文件和目录,形成树状的文件系统结构。/etc、/home、/usr
- 链接文件(l)又称符号链接,指向另一个文件或目录的指针,分为软链接和硬链接。软链接相当于Windows的快捷方式,指向文件的路径。源文件删除后,软链接失效。硬链接:指向文件的inode节点,多个硬链接指向同一个inode。ln -s /etc/passwd mypasswd创建一个指向/etc/passwd的软链接
- 块设备文件(b)用于访问块设备,如硬盘、U盘等。块设备以固定大小的块为单位进行数据传输。如/dev/sda、/dev/sdb。
- 字符设备文件(c)用于访问字符设备,如键盘、鼠标、串口等,字符设备以字节为单位进行数据传输。/dev/tty
- 管道文件(p):用于进程间通信,提供一个单向的数据流。mkfifo mypipe创建一个名为mypipe的管道文件。
- 套接字文件(s):用于网络通信,实现进程间通过网络进行数据交换。/tmp/mysocket。
使用ls -l查看文件类型,输出的第一个字符就是。
file
/usr目录
在Linux系统中,/usr目录是一个非常重要的目录,是Unix System Resource的缩写。
- /usr/bin:存放用户和系统管理员使用的大多数可执行文件,这些文件并不是系统启动时必须的,而是日常操作和应用软件需要用到的命令。
- /usr/sbin:存放系统管理员使用的管理命令,这些命令通常用于系统维护和管理。
#include <head.h> 和 #include “head.h” 的区别
#include 指令用于包含头文件,将头文件的内容复制到当前源文件中。
#include <head.h>
- 编译器会优先在系统指定的标准头文件目录中查找名为head.h的文件。这些目录通常包含标准库的头文件,如stdio.h,stdlib.h等。
#include “head.h”
- 编译器会首先在当前源文件所在的目录中查找名为head.h的文件,如果找不到,再按系统指定的规则到其它目录中查找。
Linux线程间的同步与互斥方法
在Linux系统中,多线程编程非常常见,但多个线程同时访问共享资源时,很容易引发数据竞争问题。为了确保程序的正确性和稳定性,必须采用合适的同步与互斥机制。
- 同步:指多个线程按照一定的顺序执行,确保某些特定的处理顺序。
- 互斥:指在某一特定时间段内只允许一个线程访问共享资源。
互斥锁
一次只能被一个线程持有。
信号量
信号量是一个计数器,可以实现多个线程对多个共享资源的访问。
条件变量
条件变量与互斥锁配合使用,允许线程等待某个特定的条件的发生。实现线程间的同步,一个线程可以等待另一个线程通知它某个条件已经满足。
读写锁
读写锁允许多个线程同时读取共享资源,但同一个时间只允许一个线程写入。
自旋锁
自旋锁是一种忙等待的锁,当一个线程试图获取已经持有的锁时,会一直忙等待,直到释放。
互斥锁:适合保护单个共享资源,确保互斥访问。
信号量:适合控制多个线程对有限数量资源的访问。
条件变量:适合实现线程间的同步,等待特定条件。
读写锁:适合读操作远多于写操作的场景。
自旋锁:适合锁持有时间较短的场景,但过度使用可能导致CPU空转。
线程拥有自己的堆栈和局部变量
排序算法中哪些是不稳定的
不稳定的排序算法是指排序过程中,无法保证相同值的元素的相对顺序不会改变。
- 快速排序
- 选择排序
- 希尔排序
将序列分为若干子序列,分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。 - 堆排序
在堆调整的过程中,可能会破坏相等元素的相对顺序。
对于大量数据的传输,共享内存通常是进程间同步的最佳选择。
共享内存直接映射到进程的地址空间,省去了内核态和用户态的切换,数据传输效率极高。
共享内存可以支持非常大的数据传输,不受限制。
进程可以直接对共享内存进行读写操作,就像访问自己的内存一样。
- 一个进程创建一个共享内存段,并指定其大小。
- 其它进程通过共享内存段的标识符获取到该共享内存段,并将其映射到自己的地址空间。
- 各个进程可以直接对映射的共享内存进行读写操作,实现数据交换,
- 为了保证多个进程对共享内存的访问是同步的,通常会结合信号量等同步机制来实现。
管道:单向数据流,适合用于父子进程间的通信。
用C++写一个单例类
编写一个单例类(Singleton)在C++中非常常见,单例模式确保一个类中只有一个实例,并提供一个全局访问点。
class Sigleton{
private:
//私有构造函数,防止直接实例化
Singleton(){
std::cout << "Singleton istance created." << std::endl;
}
//防止拷贝构造和赋值操作
Singleton(const Sigleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
//获取单例实例的静态方法
static Sigleton& getInstance(){
static Singleton istance;
return instance;
}
void doSomething(){
std::cout << "Doing something with the Singleton instance." << std::endl;
}
};
int main(){
// 获取单例实例,并调用其方法
Singleton& instance = Singleton::getInstance();
instance.doSomething();
// 再次获取单例实例,验证是否是同一个对象
Singleton& anotherInstance = Singleton::getInstance();
anotherInstance.doSomething();
}
C++中重载与覆盖的区别
重载
在同一个作用域内,多个函数具有相同的名字,但是参数列表不同(参数的个数、类型或顺序不同)。
通过不同的参数列表来实现功能的重用。
编译器根据函数调用时的实参列表来选择合适的函数。
覆盖
在继承体系中,子类重新定义了基类中的虚函数,函数名、参数列表和返回值类型都相同。
实现多态性,根据对象的实际类型来调用不同的函数。
基类中的函数必须声明为虚函数(virtual),子类中重写该函数。
- 作用域:重载在相同作用域。覆盖在不同作用域(基类和子类)。
- 函数名:都相同。
- 参数列表:重载不同,覆盖相同。
- 返回值:重载可以不同,覆盖相同。
- 覆盖实现多态性。
- 重载是编译时确定,覆盖是运行时确定。
全局变量和局部变量在内存中的区别
存储位置
- 全局变量:通常存储在全局数据区(静态存储区)。这区域在程序编译时就分配了内存,直到程序结束后才释放。
- 局部变量:存储在栈区。当函数被调用时,栈会为局部变量分配内存;当函数执行完毕后,这些内存会释放。
生命周期
- 全局变量:从程序运行开始到程序结束,整个生命周期都存在于内存中。
- 局部变量:只在函数或代码块执行时存在。当函数执行结束或代码块退出后,局部变量的内存会释放。