0%

pdd服务端研发一面凉经

0425 问了些八股(10min),没问项目,做了三题手撕(50min)
在pdd的面试平台,需要共享屏幕。面试官很和蔼,比约的时间早进面试间。
不会的也说没事,手撕也提示

0429更新,已经挂了QAQ

自我介绍(1min)

  1. 常见的集合有哪些
  2. 哈希表有哪些实现方式
  3. 除了哈希表,还有什么数据结构能实现输入key,找到他的value
    位图、布隆过滤器能查key是否存在
    前缀树找字符串是否存在
    跳表、平衡树找有序数据
  4. 哈希表怎么实现的
  5. 为什么链表长度大于8,要用红黑树
    在链表中查找元素需要逐个遍历,时间复杂度为 _O(n)_。如果哈希冲突严重(例如大量元素映射到同一个桶),链表会变得很长,导致查询性能急剧下降。
    在正常情况下,链表长度很少会达到8。根据泊松分布统计,长度达到8的概率约为 0.00000006(即千万分之一)。因此,这种转换是针对极端情况的优化。
  6. 为什么红黑树是log(n)的时间复杂度
    红黑树是自平衡的二叉搜索树,红黑树通过颜色规则和旋转操作保持近似平衡
  7. 优先队列怎么实现的
    用堆实现的,大根堆:父节点的值大于两个子节点
  8. 说一下堆怎么实现的,pop之后堆怎么调整
    小根堆的建立:堆一般用数组实现,父节点下标为i,子节点的下标为2i + 1, 2i + 2。从最后一个非叶子节点开始调整,从父节点的子节点中选择一个小于父节点的节点,交换位置,递归再往下调整。
    小根堆的插入:将新元素插入在数组末尾,将新节点和它的父节点开始比较,如果新节点比父节点小,就交换两者;继续这个过程,知道满足堆的要求
    小根堆的删除:将堆顶元素和末尾元素进行交换,移除末尾元素(也就是前堆顶),将新堆顶比较、下沉,直到满足堆的要求。
  9. 说一下TCP协议
  10. 说一下IP协议
  11. 说一下HTTP协议和TCP/IP整体关系
    HTTP是TCP/IP协议栈的上层协议,专注于Web应用逻辑,而TCP/IP负责底层数据传输和网络互联。
    HTTP将请求/响应消息封装成TCP报文段
    IP协议将TCP报文分段为数据包,通过路由选择送达目标服务器
  12. 说一下HTTPS
  13. 说一下HTTPS的原理
  14. 说一下公钥、私钥和数字证书
  15. 为什么一开始要用非对称加密,后来用对称加密
  16. HTTPS 如何防范中间人攻击

手撕

  1. 给了两个有序的数组(数组中有重复元素),把a中存在,但b中不存在的数据加入新数组,利用a和b有序的性质。
    双指针,一个指针指向a,一个指针指向b
  2. 一个有序序列构造成二叉搜索树有很多种结构,如何判断两个二叉搜索树是一样的。
    先说了中序遍历获得序列判断,但面试官提示说能不能一边遍历一边判断,比如最小的数不相等就不往下遍历了
  3. 给一个字符数组,写一个排序函数,数字排在字母前面,但数字之间相对顺序不变,字母之间相对顺序不变。(原地排序,不能用新数组)

一些问题的代码

构建小根堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void heapify(int arr[], int n, int i) {
int smallest = i; // 初始化当前节点为最小值
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点

// 找到当前节点、左子节点、右子节点中的最小值
if (left < n && arr[left] < arr[smallest])
smallest = left;
if (right < n && arr[right] < arr[smallest])
smallest = right;

// 若最小值不是当前节点,则交换并递归调整
if (smallest != i) {
swap(arr[i], arr[smallest]);
heapify(arr, n, smallest); // 继续向下调整
}
}

// 构建小根堆
void buildMinHeap(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i); // 从最后一个非叶子节点开始调整
}
}

小根堆插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```java
public int insert(int[] heap, int size, int val) {
if (size >= heap.length) {
throw new RuntimeException("Heap is full");
}
heap[size] = val; // 插入到末尾
int child = size;
int parent = (child - 1) / 2; // 父节点下标
// 上浮调整:若子节点小于父节点则交换
while (child > 0 && heap[child] < heap[parent]) {
swap(heap, child, parent);
child = parent;
parent = (child - 1) / 2;
}
return size + 1; // 返回新堆大小
}

小根堆删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public int deleteMin(int[] heap, int size) {
if (size <= 0) {
throw new RuntimeException("Heap is empty");
}
heap[0] = heap[size - 1]; // 末尾元素覆盖堆顶
size--;
// 下沉调整
int parent = 0;
while (true) {
int left = 2 * parent + 1; // 左子节点
int right = 2 * parent + 2; // 右子节点
int min = parent;
// 找到父节点、左右子节点中的最小值
if (left < size && heap[left] < heap[min]) {
min = left;
}
if (right < size && heap[right] < heap[min]) {
min = right;
}
if (min == parent) break; // 堆性质已满足
swap(heap, parent, min); // 交换父节点与较小子节点
parent = min;
}
return size;
}

手撕1

双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static List<Integer> findDifference(int[] A, int[] B) {  
List<Integer> C = new ArrayList<>();
int i = 0, j = 0;

while (i < A.length && j < B.length) {
if (A[i] < B[j]) {
if(i > 0 && A[i] == A[i - 1]) {
// 跳过重复元素
i++;
continue;
}
C.add(A[i]);
i++;
} else if (A[i] == B[j]) {
// 相等时跳过公共元素
i++;
j++;
} else {
// A[i] > B[j],移动B的指针
j++;
}
}

// 处理A中剩余的元素
while (i < A.length) {
C.add(A[i]);
i++;
}

return C;
}

手撕2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public boolean isSameTree(TreeNode p, TreeNode q) {
TreeNode cur1 = p, cur2 = q;
Stack<TreeNode> st1 = new Stack<>();
Stack<TreeNode> st2 = new Stack<>();

while((cur1 != null || !st1.isEmpty()) && (cur2 != null || st2.isEmpty())) {
while(cur1 != null) {
// 一直走到最左
st1.add(cur1);
cur1 = cur1.left;
}
while(cur2 != null) {
st2.add(cur2);
cur2 = cur2.left;
}
cur1 = st1.pop();
cur2 = st2.pop();
if(cur1.val != cur2.val) {
return false;
}
cur1 = cur1.right;
cur2 = cur2.right;
}
// 检查是否同时遍历完
return cur1 == null && cur2 == null;
}

手撕3

每当遇到一个数字字符,并且它前面是一个字母字符时,就把它向前交换。
这样保证数字和字符的相对顺序不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void sortDigitsBeforeLetters(char[] arr) {  
int n = arr.length;

for(int i = 1; i < n; i ++ ) {
if(arr[i] >= '0' && arr[i] <= '9') {
char temp = arr[i];
int j = i - 1;
while(j >= 0 && arr[j] >= 'a' && arr[j] <= 'z') {
arr[j + 1] = arr[j];
j -- ;
}
arr[j + 1] = temp;
}
}
}