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; }