字节和shopee已拿意向书。
字节跳动提前批 - 后端开发 - 技术中台方向
字节提前批一面
先来个个人介绍
实习经历 介绍实习经历; Svrkit(rpc 框架)的原理; Protocol buffer 相对于 json 的优点;
计网 http 1.0、1.1、2.0 的区别 三次握手、四次挥手; accept() 等待的时候,底层的原理是啥?有哪些结构? 会有三次挥手的情况吗? 服务端和客户端同时调用 close() 会怎么样;
数据库 Innodb 中的索引用了什么数据结构?B+树 什么情况会用到哈希索引?会存到硬盘吗?
Redis (个人项目中用到了 Resis、kafka、Elasticsearch 等) Redis 的 zset 底层是啥?跳表; 讲一讲跳表; zset 还用到了哈希表,用来干啥? Redis 持久化的方式 - rdb 和 aof; rdb 结束或者 aof 重写结束的时候,子进程如何通知父进程;
Kafka 原理; 发送消息的三种方式; reblance 原理(消费者组中的数量变化时);
操作系统 linux 的虚拟内存的布局(堆、栈、内存低地址到高地址存的是什么); 进程栈和线程栈的区别?
算法题 判断单链表有没有环,有的话找出入环节点
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
}
}
我写的:
import java.util.Scanner;
public class Main {
public ListNode detechCycle(ListNode head) {
if (head == null && head.next == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null && slow != fast) {
slow = slow.next;
fast = fast.next.next;
}
if (slow == fast) {
fast = head;
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
} else {
return null;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a = in.nextInt();
System.out.println(a);
System.out.println("Hello World!");
}
}
字节提前批二面
Java
volatile 和 synchronized 关键字 - 功能和区别; ReentrantLock 和 Condition 的用法; signal() 和 signalAll() 用哪个比较好;
Linux
怎么看内存情况:free -h ;(刚好面试前看到了,你说巧不巧) free -h 显示的 buffer 和 cache 有什么区别?(超出知识范围,瞎扯了一下 CPU 和内存间的高速缓存,内存和磁盘间的写缓存,还有redo log buffer,能水就水)
计网
DNS 访问流程:本地DNS服务器 - 根DNS服务器 - 顶级域DNS服务器 - 权威DNS服务器; DNS 用的传输层协议:UDP; DNS 劫持,怎么防御:答了HTTPS; HTTPS 流程:证书 - 验证 - 用对称加密来通信
数据库
事务的四个隔离级别; 有了可重复读,为什么还需要串行化:防止幻读; 有了解 MVCC 吗:不了解。告辞;
个人项目
怎样提高网站的性能:ThymeLeaf 模板引擎的缓存;借助 Kafka 来异步处理事件;用 Redis 做缓存和存储存活期短的信息(如验证码);用 Elasticsearch 做全文搜索(不会吧不会吧,不会还有人用 MySQL 的全文索引吧,手动狗头.jpg);利用线程池的方式,一个线程处理一个请求,同时通过 ThreadLocal 给每个线程绑定当前请求对应的用户信息,比如通过拦截器的方式,请求到达时,检查cookie信息,然后。。。;
加入帖子很多,用户数量多,怎么搞:(面试官应该是想我答分库分表,但是我没答出来)
数据结构
建堆(heap)的复杂度:如果已经有全部数据,那就从下到上建堆,O(N);如果是数据流,只能从上到下建堆,O(N*logN)
算法题
给定m * n矩阵matrix,可以从任意位置开始,向上、向下、向左、向右移动,但要求下一个位置上的元素要大于当前元素,找出矩阵中最长的递增路径的长度。
我写的:
import java.util.Scanner;
public class Main {
public static int findLength(int[][] matrix, int[][] length, int i, int j) {
if (length[i][j] != 0) {
return length[i][j];
}
int result = 1;
if (i - 1 >= 0 && matrix[i - 1][j] > matrix[i][j]) {
result = Math.max(result, findLength(matrix, length, i - 1, j) + 1);
}
if (i + 1 < matrix.length && matrix[i + 1][j] > matrix[i][j]) {
result = Math.max(result, findLength(matrix, length, i + 1, j) + 1);
}
if (j - 1 >= 0 && matrix[i][j - 1] > matrix[i][j]) {
result = Math.max(result, findLength(matrix, length, i, j - 1) + 1);
}
if (j + 1 < matrix[0].length && matrix[i][j + 1] > matrix[i][j]) {
result = Math.max(result, findLength(matrix, length, i, j + 1) + 1);
}
length[i][j] = result;
return result;
}
public static int longestPath(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0] == null) {
return 0;
}
int rowSize = matrix.length;
int colSize = matrix[0].length;
int[][] length = new int[rowSize][colSize];
int result = 0;
for (int i = 0; i < rowSize; i++) {
for (int j = 0; j < colSize; j++) {
result = Math.max(result, findLength(matrix, length, i, j));
}
}
return result;
}
public static void main(String[] args) {
//Scanner in = new Scanner(System.in);
//int a = in.nextInt();
//System.out.println(a);
System.out.println("Hello World!");
}
}
字节提前批三面
之前面试中不熟悉的知识点,有没有去康康:有; 来讲讲:讲了MVCC,undo log,两段锁协议等等;
一面中http 1.0/1.1/2.0答得没有很好,回去有看吗:看了,(可看我的总结:blog.csdn.net/alexkx/arti… )
TCP连接中,影响带宽的因素有哪些?(我答了发送到缓存的速度,对方读缓存的速度,拥塞控制等等)
编程语言,数据库,计网,操作系统,哪个最熟:瑟瑟发抖;
(中间有的问题忘了)
写个 sql 吧: 给定一个表 T(id, name, age, sex, city),写出一下查询语句:男性平均年龄最高的城市。
select city
from (select avg(age) as A, city
from T
where sex = "male"
group by city) as Temp
where A >= (select Temp.A from Temp);
select city
from (select avg(age) as A, city
from T
where sex = "male"
group by city) as Temp
order by A desc
limit 0,1;
做个概率题 一种流行病患病概率是1%,有一种检测试纸,检测的准确率是99%,我现在试纸检测阳性了,请问我患病的概率有多大? (概率论有点忘了)
再来做两题: 用一个[0,4)的随机数生成器rand4(),模拟一个[0.6)的随机数rand6() - 常见题,直接口述
32个石块,重量不等,用天平来进行衡量,找出最重的石块,最少需要几次? - 考官提示了一下,用归并的方法,应该是31次
如果是找出最重的两块,需要几次?我答了46次,看网上有的人说31次,感觉不是很对。
shopee 后端开发
笔试(2020.7.29)
10题计算机相关的选择题 5题数学相关的选择题
3题编程题:
- 用多重背包算法来做
- 根据给出的数据建二叉搜索树,在返回所有叶子节点
- k个一组翻转链表 - leetcode25 (AC了2.8题)
一面
进程间的通信方式:管道(匿名和命名)、消息队列、共享内存&信号量、信号;ipcs 查看 ipc 对象;
内核空间和用户空间;为啥要这么区分;
分段机制、分页机制;
TCP四次挥手;
TCP 流量控制和拥塞控制;
输入 www.shopee.com 以后的过程:DNS查询、TCP 三次握手、验证证书、生成对称加密的秘钥,如果是 http/2.0,服务器还会主动推送相关资源。
websocket 有了解吗:没有;
IO 多路复用的几种系统调用(select、poll、epoll),主要区别;
介绍一下红黑树;
介绍一下 B+树、B树;
Java 多线程实现方式(就是怎么用多线程执行任务,比如实现 Runnable接口,实现 Callable 接口,继承 Thread 类);
MySQL的持久化手段,比如执行update 语句时,如何保证数据持久化:答了redo log,binlog,undo log等等;
ACID 是啥;
Redis 的 sorted set 底层的数据结构(跳表),原理;
Kafka 原理,怎么保证高可用、高性能:分区、主从备份、顺序IO、每个消息只在分区主节点保存一份等等;
最后是一题简单题:
在字符串中找出连续最长的数字串
样例输出
输出123058789,函数返回值9
输出54761,函数返回值5
接口说明
函数原型:
unsignedint Continumax(char** pOutputstr, char* intputstr)
输入参数:
char* intputstr 输入字符串;
输出参数:
char** pOutputstr: 连续最长的数字串,如果连续最长的数字串的长度为0,应该返回空字符串;如果输入字符串是空,也应该返回空字符串;
返回值:
连续最长的数字串的长度
输入描述
输入一个字符串。
输出描述
输出字符串中最长的数字字符串和它的长度。如果有相同长度的串,则要一块儿输出,但是长度还是一串的长度
示例1
输入
abcd12345ed125ss123058789
输出
123058789,9
import java.util.Scanner;
public class Main {
public static int read(char[] arr, int begin) {
int cur = begin;
while (cur < arr.length) {
if (arr[cur] <= '9' && arr[cur] >= '0'){
cur++;
} else {
break;
}
}
return cur;
}
public static void Continumax(String input) {
if (input == null || input.length() == 0) {
System.out.println(", 0");
}
char[] arr = input.toCharArray();
int resultBegin = -1;
int resultSize = 0;
int i = 0;
while (i < arr.length) {
if (arr[i] <= '9' && arr[i] >= '0'){
int end = read(arr, i);
if (end - i > resultSize) {
resultSize = end - i;
resultBegin = i;
}
i = end;
} else {
i++;
}
}
if(resultSize > 0) {
System.out.printf("%s, %d", input.substring(resultBegin, resultBegin + resultSize), resultSize);
} else {
System.out.println(", 0");
}
}
public static void main(String[] args) {
//Scanner in = new Scanner(System.in);
//int a = in.nextInt();
//System.out.println(a);
String input = "abcd12345ed125ss123058789";
Continumax(input);
}
}
二面
问实习;
ConcurrentHashMap 的 get、put方法;为什么插入新节点的时候是用尾插法;
web安全有了解吗:讲了下注入攻击和权限管理; CSRF(跨站域请求伪造):不太了解
讲讲 MVCC;(推荐看这篇:blog.csdn.net/waves___/ar… )
Kafka 的 partition 的分配策略,就是同一个 topic 的 partition 具体分配给某个订阅它的消费者组的哪个消费者; Kafka 的 rebalance;
TCP 的 CLOSE_WAIT 和 TIME_WAIT 状态; CLOSE_WAIT 的作用;
Linux 用什么命令看 tcp 的状态信息:netstat;
算法题:
- 对一个数组进行排序,元素只有0,1,2:我答了用类似快排的方式;
- 找一个字符串里面最长回文子串:我简单讲了一下Manache算法 ,然后详细讲了动态规划的方式。
你觉得你技术方面最大的优势是什么? 职业规划?
反问环节: 进了以后怎么选择组:公司是统一招聘的,入职的时候再分配,会考虑到个人意愿。
微信
一面(2020.8.10)
上来先做两题:
- 倒转单链表(在原链表上倒转)
struct LinkNode {
int value;
struct LinkNode * next;
};
void reverseList( struct LinkNode * head );
注意:翻转后,head 指针仍然要指向链表头部。所以原来的头节点对象,翻转后,仍然需要是头节点。(可以通过修改节点的值来实现)
import java.util.Scanner;
public class Main {
public static void reverseList(LinkNode head) {
if (head == null || head.next == null) {
return;
}
// 从第二个节点开始翻转
LinkNode cur = head.next;
LinkNode next = null;
// 创建一个新节点,作为翻转后的最后一个节点
LinKNode pre = new LinkNode();
pre.value = head.value;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
head.value = pre.value;
head.next = pre.next;
}
public static void main(String[] args) {
}
}
- 数字1-N 分布在长度为N+1的数组里面,一个元素重复1次,请求重复的元素? 时间复杂度O(N) 空间复杂度O(1)
用异或来做就完事了
(面试官是之前实习的 leader) 讲讲 Java 中的 new 和 C++ 中的 malloc() 的区别; 讲讲 Java 的垃圾回收机制(这里我水了超久); 进程和线程的区别; 为什么进程切换会比线程切换消耗的资源要多? epoll() 和 select() 的区别; TCP 三次握手和四次挥手; 两个进程分别监听 TCP 和 UDP,可以监听同一个端口吗? TIME_WAIT 状态的作用;
二面(2020.8.14)
Elasticsearch 的原理:我讲了下分词和倒排索引; Java 的 HashMap 和 TreeMap; 数据库有哪些索引; 讲讲平衡搜索树;
输入www.qq.com以后的过程:讲了下DNS,http,https,http/2.0,tcp,ip,链路层(ARP协议)等内容;
算法题: 1、单机里面有多个文件,每个文件包含若干个整数,找出这些文件中的值最大的k个数: 讲了下单线程和多线程的处理方式,单线程直接用一个小根堆,然后遍历每个文件;多线程的话,就对每个文件建一个小根堆,取得这个文件的top k,然后对这 k 个数排序,最后将每个文件拿到的结果来建一个大根堆,依次取出top k。
2、给定一个数组,里面的元素先是升序,再降序,比如 [1, 3, 5, 4, 2],找出最大的元素的下标: 我用了下二分法来做:
public class Main {
public static int find(int[] nums, int left, int right) {
if (left == right) {
return left;
}
int mid = left + ((right - left) >> 1);
if (nums[mid] > nums[mid + 1]) {
return find(nums, left, mid);
} else {
return find(nums, mid + 1, right);
}
}
public static int find(int[] nums) {
if(nums == null || nums.length == 0) {
return -1;
}
return find(nums, 0, nums.length - 1);
}
public static void main(String[] args) {
int[] nums = new int[]{10, 15, 20, 20, 21, 22, 35, 20, 10, 9, 8, 7, 6};
System.out.println(find(nums));
}
}
三面(2020.8.18)
一共面了三个小时,前两个小时做题,最后一个小时问答。我人都傻了。
一共出了5题,中间跑测试样例以及面试官换题花了点时间,只做出了前4题,最后一题没写完。
1、LRU
/*
1. 某操作系统采用 LRU 作为内存页面置换算法。
假设初始内存为空,现给定将访问的内存页序列 pages, 序列长度 page_cnt 和内存总容量(页面数) mem,请返回缺页中断的次数。
例如:序列 pages = 1, 2, 3, 2, 1, 4; 内存 mem = 2
返回:5
int check_page_faults(int pages[], int page_cnt, int mem);
*/
import java.io.*;
import java.util.*;
class LRU {
class Node{
int key;
Node pre;
Node next;
Node(int key, Node pre, Node next) {
this.key = key;
this.pre = pre;
this.next = next;
}
}
int capacity;
int size;
Node head = new Node(-1, null, null);
Node tail = new Node(-1, null, null);
Map<Integer, Node> map = new HashMap<>();
{
head.next = tail;
tail.pre = head;
size = 0;
}
public LRU(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException();
}
this.capacity = capacity;
}
public boolean access(int key) {
boolean result = false;
Node node = map.get(key);
if (node != null) {
// 命中
result = true;
moveToHead(node);
} else {
// 缺页
if (size == capacity) {
// LRU 已满
Node last = removeLast();
map.remove(last.key);
size--;
}
node = new Node(key, null, null);
map.put(key, node);
addToHead(node);
size++;
}
return result;
}
private void addToHead(Node current) {
Node oldHead = head.next;
head.next = current;
current.pre = head;
current.next = oldHead;
oldHead.pre = current;
}
private void remove(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
}
private void moveToHead(Node node) {
remove(node);
addToHead(node);
}
private Node removeLast() {
if (size == 0) {
return null;
}
Node last = tail.pre;
remove(last);
return last;
}
}
public class MyCode {
public static int check_page_faults(int pages[], int page_cnt, int mem) {
int result = 0;
if (pages == null || page_cnt <= 0 || pages.length < page_cnt) {
return result;
}
LRU lru = new LRU(mem);
for (int i = 0; i < page_cnt; i++) {
boolean isIn = lru.access(pages[i]);
if (!isIn) {
result++;
}
}
return result;
}
public static void main (String[] args) {
int[] pages = new int[]{1, 1, 1,1,1,1};
int mem = 2;
System.out.println(check_page_faults(pages, pages.length, mem));
}
}
2、表达式比较
注意:这里不能直接把 a-z 当成 1-26 来计算,如果这样做的话,第五个例子就会错了。
/*
2. 检查两个表达式是否等价。表达式仅包含小写字母 'a'-'z', '+', '-', '(', ')',且表达式里的未知数仅有一个字符。
例如:
1) exp1 = "a+b+c-a", exp2 = "(b+c)", result: true
2) exp1 = "a-b-c", exp2 = "a-(b+c)", result: true
3) exp1 = "a-b+c", exp2 = "a-(b+c)", result: false
4) exp1 = "a-b+c", exp2 = "a-(b-(c-d)-d)", result: true
5) exp1 = "a+d", exp2 = "b+c", result: false
bool check(const char* exp1, const char* exp2);
*/
public class MyCode {
public static int process(char[] arr1, int begin, int[] count) {
// int[] result = new int[26];
int i = begin;
while (i < arr1.length) {
if (arr1[i] == '(') {
// 处理括号情况
int[] child = new int[26];
int next = process(arr1, i + 1, child);
for (int j = 0; j < 26; j++) {
if (i > 0 && arr1[i - 1] == '-') {
// 负号
count[j] -= child[j];
} else {
// 括号前为正号
count[j] += child[j];
}
}
i = next;
} else if (arr1[i] == ')'){
i++;
return i;
} else {
if (arr1[i] <= 'z' && arr1[i] >= 'a') {
if (i > 0 && arr1[i - 1] == '-') {
count[arr1[i] - 'a'] -= 1;
} else {
count[arr1[i] - 'a'] += 1;
}
}
i++;
}
}
return i;
}
public static boolean check(String str1, String str2) {
char[] arr1 = str1.toCharArray();
char[] arr2 = str2.toCharArray();
int[] result1 = new int[26];
int[] result2 = new int[26];
process(arr1, 0, result1);
process(arr2, 0, result2);
for (int i = 0; i < 26; i++) {
if (result1[i] != result2[i]){
return false;
}
}
return true;
}
public static void main(String[] args) {
String str1 = "a+b+c-a";
String str2 = "(b+c)";
System.out.println(check(str1, str2));
String str3 = "a-b-c";
String str4 = "a-(b+c)";
System.out.println(check(str3, str4));
String str5 = "a-b+c";
String str6 = "a-(b+c)";
System.out.println(check(str5, str6));
String str7 = "a-b+c";
String str8 = "a-(b-(c-d)-d)";
System.out.println(check(str7, str8));
String str9 = "a+d";
String str10 = "b+c";
System.out.println(check(str9, str10));
}
}
3、恢复 IP 地址
/*
3. 给一个由数字组成的字符串,求出所有可能的 IP 地址。
例如:给出字符串 "25525511135",所有可能的 IP 地址为:
[ "255.255.11.135", "255.255.111.35" ]
vector<string> restore_ip_addrs(const string& s);
*/
import java.util.*;
public class Main1 {
static void process(String s, int sectionNum, int begin, String ip, List<String> result) {
if (sectionNum == 4) {
if (begin == s.length()) {
result.add(ip.substring(0, ip.length() - 1));
}
return;
}
if (sectionNum > 4 || (4 - sectionNum) * 3 < (s.length() - begin)) {
return;
}
for (int i = 1; i <= 3; i++) {
if (begin + i > s.length()) {
break;
}
String nextSection = s.substring(begin, begin + i);
if (Integer.parseInt(nextSection) > 255) {
return;
} else {
process(s, sectionNum + 1, begin + i, ip + nextSection + ".", result);
}
}
}
static List<String> restore_ip_addrs(String s) {
List<String> result = new ArrayList<>();
if (s == null || s.length() < 4 || s.length() > 12) {
return result;
}
process(s, 0, 0, "", result);
return result;
}
public static void main(String[] args) {
String str = "25525511135";
List<String> result = restore_ip_addrs(str);
for (String s : result) {
System.out.println(s);
}
Map<String, String> map = new HashMap<>();
for(Map.Entry e : map.entrySet()) {
e.getValue();
}
}
}
4、
/*
4. 对输入的数组按出现的频率进行排序,若出现频率一致则按数字升序排序。
例如:1, 2, 4, 9, 4, 1, 4, 2, 结果为:4, 4, 4, 1, 1, 2, 2, 9
void sort(int arr[], int n);
*/
import java.util.*;
class MyCode{
static void sort(int[] arr, int n) {
if (arr == null || arr.length < 0) {
return;
}
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
Integer value = map.get(arr[i]);
if (value == null) {
map.put(arr[i], 1);
} else {
map.put(arr[i], value + 1);
}
}
int[][] result = new int[map.size()][];
int i = 0;
for (Map.Entry<Integer, Integer> e : map.entrySet()) {
result[i++] = new int[]{e.getKey(), e.getValue()};
}
Arrays.sort(result, (int[] a, int[] b) -> {
if (a[1] == b[1]) {
return a[0] - b[0];
} else {
return b[1] - a[1];
}
});
int cur = 0;
for (i = 0; i < result.length; i++) {
for (int j = 0; j < result[i][1]; j++){
arr[cur++] = result[i][0];
}
}
}
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 4, 9, 4, 1, 4, 2};
sort(arr, arr.length);
for(int i = 0; i < arr.length; i++) {
System.out.printf("%d, ", arr[i]);
}
}
}
5、
这题我还没做完,只贴个题目吧
/*
5. 给定一个排序链表,删除所有重复的元素,只留下原链表中没有重复的元素。
例如: 1->1->2->3->3->4->4->5->6->6->null, return: 2->5->null
struct LinkNode {
int value;
struct LinkNode * next;
};
struct LinkNode* delete_duplicates(struct LinkNode* head);
*/
-----------------------
做完这些题以后,休息了五分钟,然后开始问操作系统、计网、Redis:(只记得一部分) 进程和线程的区别; 什么时候用多线程,什么时候用多进程; 32位和64位系统中,进程虚拟内存的布局; 进行系统调用时的过程;
介绍一下Redis; Redis 的内存布局;(不会) 为什么Redis单线程也这么快:I/O复用,使用子进程去处理耗时多的任务(如rdb快照,aof文件改写);
select / epoll; TCP握手过程; 监听未完成的连接时(listen阶段),如何防止DDoS;
四面(2020.8.21)
问的很深,把我问傻了。只记得部分问题:
问了实习内容; 内核态和用户态的区别?为啥要区分内核空间和用户空间?怎么实现内核态?和底层的CPU指令有关吗? 进程间通信方式?(面试官说有十多种。。。我迷惑了) UnixSocket; sleep() 等定时机制,定时精度是由什么因素决定的?怎么实现? 睡眠的进程/线程怎么被唤醒;
堆排序;
Mysql 联合索引的结构;
找了一下,进程间通信大概有下面这些类别:(参考《Linux/UNIX系统编程手册》 book.douban.com/subject/258… )