10月8号 虾皮、喜马拉雅秋招笔试记录
虾皮
有单选,多选,3道编程。只有最后一题编程偏难
相关知识点记录:
- cat grep chmod的区别
- 下列哪个属于对称加密:AES,DES,RSA,MD5
- 选择排序和堆排序是否是稳定的
- 二者都不稳定。在选择排序过程中,最小(或最大)元素会被选出并与当前排序部分的最后一个元素交换。堆排序通过构建堆来实现排序,而在堆的操作过程中,元素的位置可能会发生变化,特别是当有相同值的元素在堆中时,它们的相对顺序可能会被打乱。因此,堆排序也是不稳定的。
- 分布式系统的CAP理论
AES 和 DES 都属于对称加密。
而 RSA DSA是一种非对称加密算法,MD5 是一种哈希算法
第三题的编程题目:
给你一个整数数组nums,找出并返回所有该数组中不同的递增子序列个数,递增子序列中至少有两个元素
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
例如,若数组为{4,6,7,7},则答案应为8
喜马拉雅
有单选,多选,2道编程。共一小时。
相关知识点记录:
- 广度优先除了队列还可以用什么数据结构=》链表,数组
- 归并排序是否会退化,最坏情况是什么=》无论输入数据的排列方式如何,归并排序的时间复杂度始终是 O(n log n)。并且它是稳定的
- 迪杰斯特拉是深度优先吗=》不是。是贪心算法
- 什么情况会发生page fault=》1.缺页,当进程试图访问某个页面时,该页面并不在物理内存中,而是在硬盘。这可能是由于:a.当一个程序第一次被加载到内存时,其所有的页面并不一定会被立即加载 b.页面被换出 2.访问被保护的页面
此时会发生什么:1.触发中断,进入内核态,由操作系统接管控制权 2.确定缺失的页面的物理地址,将页面从硬盘加载到内存,如果内存已满则会有换页 3.更新页表以反映新加载的页面 4.恢复之前被暂停的进程,重新执行导致页面错误的指令
- nginx支持反向代理 负载均衡 虚拟主机吗
反向代理 是指客户端的请求先到达代理服务器(Nginx),然后由代理服务器将请求转发到后端的实际服务器上。Nginx 作为反向代理具有以下优点:
- 隐藏真实服务器:客户端只与 Nginx 交互,后端服务器的 IP 地址被隐藏,增强了安全性。
- SSL 终止:可以在 Nginx 中处理 SSL 加密,从而减轻后端服务器的负担。
- 请求分发:Nginx 可以根据自定义规则将请求分发到不同的后端服务器。
主要的负载均衡方法包括:
- 轮询(Round Robin):默认方法,按顺序将请求分发到后端服务器。
- 最少连接(Least Connections):将请求发送到当前连接数最少的服务器。
- IP 哈希(IP Hash):根据客户端 IP 地址进行哈希,将请求路由到特定的服务器,适合需要会话保持的场景。
虚拟主机 是指在同一台服务器上配置多个网站或应用。Nginx 支持基于主机名和 IP 地址的虚拟主机配置。
意义:资源共享。多个域名可以共享同一个 IP 地址,但每个域名都可以配置不同的 SSL 证书。这样就不需要为每个网站购买独立的 IP 地址。
具体功能如下:
- 基于主机名的虚拟主机:根据请求的
Host头部信息,将请求转发到不同的后端服务。例如,你可以为example.com和example.org配置不同的后端。- 基于 IP 地址的虚拟主机:可以根据请求的 IP 地址来处理不同的请求。
- SSL协议握手的时候要交换哪些信息
握手的目的是为了建立一个安全的连接,并在客户端和服务器之间协商加密参数。
客户端 Hello 消息
客户端向服务器发送一条
ClientHello消息,内容包括:
- 协议版本:支持的 SSL/TLS 版本(如 TLS 1.2, TLS 1.3 等)。
- 随机数:一个随机生成的数值,用于生成会话密钥。
- 加密套件列表:客户端支持的加密算法组合,包括对称加密、非对称加密和散列函数的列表。
- 压缩方法:支持的压缩算法(如果使用)。
- 扩展信息:如支持的 Server Name Indication (SNI),用于指示服务器名等。
2. 服务器 Hello 消息
服务器回应一个
ServerHello消息,内容包括:
- 协议版本:选择的 SSL/TLS 版本(通常是客户端支持的最高版本)。
- 随机数:服务器生成的随机数,用于密钥生成。
- 加密套件:服务器选择的加密算法组合。
- 压缩方法:服务器选择的压缩算法。
- 扩展信息:可能包含关于会话恢复或其他参数的信息。
3. 服务器证书
- 服务器发送其数字证书,包含服务器的公钥以及由证书颁发机构 (CA) 签名的信息。客户端使用此证书验证服务器的身份。
4. 证书链
- 服务器可能还会发送一系列中间证书,以便客户端可以验证服务器证书的有效性。整个证书链必须由客户端信任的根证书颁发。
5. 服务器密钥交换(可选)
- 如果使用的加密算法(如 Diffie-Hellman)需要服务器提供额外的参数,服务器会在此阶段发送这些参数。
6. 服务器 Hello Done
- 服务器发送
ServerHelloDone消息,表示其所有的 Hello 消息已发送完毕,并准备接收客户端的响应。7. 客户端密钥交换
- 客户端生成一个预主密钥(Pre-Master Secret),并用服务器的公钥加密后发送给服务器。只有服务器可以使用私钥解密此信息。
8. 改变密码规范(Change Cipher Spec)
- 客户端发送
ChangeCipherSpec消息,通知服务器后续消息将使用协商的加密算法和密钥。9. 握手完成(Finished)
- 客户端发送一个
Finished消息,表示客户端的握手消息已结束。此消息经过加密,确认所有的握手信息没有被篡改。10. 服务器也进行 Change Cipher Spec 和 Finished
- 服务器收到客户端的
Finished消息后,也发送自己的ChangeCipherSpec消息,随后发送Finished消息,确认握手完成。
编程
第一题90%通过 字符串s里面,找最多出现k种字母的子串,满足条件的最长子串有多长
public static int longestSubstring (String s, int k) {
//贪心算法,随时记录start和end,用哈希表存每个字母上次出现的位置
if(k==0)
return 0;
Map<Character,Integer> map=new HashMap();
int ans=0;
int len=s.length();
int start=0,count=0;
int i=0;
while(i<len){
char c=s.charAt(i);
if(map.getOrDefault(c,-1)<start){
//说明前面没出现过
count++;
if(count>k){
//说明这个地方不能留
ans=Math.max(ans,i-start);
char first=s.charAt(start);
start=map.get(first)+1;//找到这个字母最后出现的位置
if(start==len)break;
count=1;i=start;c=s.charAt(i);map.clear();
}
}
map.put(c,i);
i++;
}
//最后再判断一遍
if(count<=k&&count>0)
ans=Math.max(ans,len-start);
return ans;
}
上述代码存在的问题:窗口每次收缩太多了
GPT提供的答案:
public static int longestSubstring2(String s, int k) {
if (k == 0) return 0;
Map<Character, Integer> map = new HashMap<>();
int ans = 0;
int start = 0;
for (int end = 0; end < s.length(); end++) {
char endChar = s.charAt(end);
map.put(endChar, map.getOrDefault(endChar, 0) + 1);//用map来计数
// 当窗口中的不同字符超过k时,通过while循环收缩窗口,步长为1 确保不会把start移到太后面
while (map.size() > k) {
char startChar = s.charAt(start);
map.put(startChar, map.get(startChar) - 1);
if (map.get(startChar) == 0) {
map.remove(startChar);
}
start++;
}
// 更新最大长度
ans = Math.max(ans, end - start + 1);
}
return ans;
}
第二题AC 是找数组中单个出现的元素
public static int singleElement (ArrayList<Integer> v) {
// write code here
int ans=0;
for(int i:v)
ans=ans^i;
return ans;
}
如果用二分查找:
public static int findSingleNumber(int[] nums) {
// 首先,确保数组是有序的。如果输入数组已经有序,则这步可以忽略
// Arrays.sort(nums); // 如果数组不保证有序,则需要先排序
int left = 0;
int right = nums.length - 1;
while (left < right) {
// 找到中间位置
int mid = left + (right - left) / 2;
// 确保 mid 是偶数,方便判断成对的数字
if (mid % 2 == 1) {
mid--; // 如果 mid 是奇数,则向下调整到前一个
}
// 判断 mid 和 mid + 1 的值是否相同
if (nums[mid] == nums[mid + 1]) {
// 如果相同,说明只出现一次的数字在右侧
left = mid + 2;
} else {
// 如果不同,说明只出现一次的数字在左侧(可能是 mid,或者在 mid 之前)
right = mid;
}
}
// left 应该是只出现一次的数字的位置
return nums[left];
}
进一步地,如果有两个数字只出现一次:
- 通过异或所有数字,得到这两个只出现一次的数字的异或结果。
- 找到这个结果的某一位为 1 的位(即这两个数字的某些位不同),利用这个位将数组分为两部分,以此可以分开这两个只出现一次的数字。
- 分别对这两部分进行异或运算,最终得到这两个数字。
public static int[] findSingleNumbers(int[] nums) {
int xor = 0;
// 首先,对所有数字进行异或,得到两个只出现一次的数字的异或值
for (int num : nums) {
xor ^= num;
}
// 找到异或结果中一个为 1 的位
int diffBit = xor & (-xor); // 取得最低位的 1
int[] result = new int[2];
// 根据 diffBit 将数字分成两个组
for (int num : nums) {
// 根据 diffBit 进行分组
if ((num & diffBit) == 0) {
result[0] ^= num; // 第一个组
} else {
result[1] ^= num; // 第二个组
}
}
return result;
}
查看14道真题和解析
