这些是腾讯光子游戏客户端面试中常见的算法与数学题。下面按题目分别给出思路和参考代码(C++)。
1. 如何获得链表中倒数第 k 个数
思路:快慢指针(双指针)
- 定义两个指针
fast和slow,都指向头节点。 fast先向前走k步。- 然后
fast和slow同时向前走,直到fast走到链表末尾(nullptr)。 - 此时
slow指向的就是倒数第k个节点。
注意边界:
- 链表为空;
k <= 0;k大于链表长度。
代码示例:
cpp
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode* findKthFromEnd(ListNode* head, int k) {
if (!head || k <= 0) return nullptr;
ListNode* fast = head;
ListNode* slow = head;
// fast 先走 k 步
for (int i = 0; i < k; ++i) {
if (!fast) return nullptr; // k 大于链表长度
fast = fast->next;
}
// 同时走
while (fast) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
复杂度:
- 时间复杂度:
O(n) - 空间复杂度:
O(1)
2. 圆内均匀随机点
圆心 (0,0),半径 R,要求给出圆内的一个随机点。
错误做法
直接随机 x ∈ [-R, R],y ∈ [-R, R],然后判断 x² + y² <= R²。
这样虽然可以,但拒绝采样效率低(约 78.5% 接受率),而且如果直接取 r = rand(0, R)、θ = rand(0, 2π),点会向圆心聚集,不是均匀分布。
正确做法:极坐标 + 面积均匀
圆内均匀分布要求概率密度与面积成正比。
令:
θ = 2π * rand01()r = R * sqrt(rand01())
因为面积正比于 r²,所以 r 的累积分布函数是 (r/R)²,反变换得到 r = R * sqrt(u)。
代码示例:
cpp
#include <cmath>
#include <cstdlib>
struct Point { double x, y; };
double rand01() {
return rand() / (double)RAND_MAX;
}
Point randomPointInCircle(double R) {
double theta = 2.0 * M_PI * rand01();
double r = R * std::sqrt(rand01());
return { r * std::cos(theta), r * std::sin(theta) };
}
验证:
- 面积均匀:
r的分布满足P(r < r0) = (r0/R)²; - 角度均匀:
θ在[0, 2π)均匀分布。
3. 快速排序的时间复杂度,最坏情况是多少
- 平均时间复杂度:
O(n log n) - 最好时间复杂度:
O(n log n) - 最坏时间复杂度:
O(n²)
最坏情况出现在:
每次选择的基准值(pivot)都是当前区间的最小值或最大值,导致划分极度不平衡。
例如:
- 数组已经有序;
- 每次取第一个或最后一个元素作为 pivot。
此时递归深度为 n,每层划分代价为 O(n),总代价 O(n²)。
优化方法:
- 随机选择 pivot;
- 三数取中;
- 小数组用插入排序;
- 三路划分处理大量重复元素。
空间复杂度:
- 平均:
O(log n)(递归栈) - 最坏:
O(n)
面试加分点
- 链表题:可以提"如果链表长度未知,快慢指针只需遍历一次;如果允许修改链表,也可以先反转再找,但会改变结构"。
- 随机点题 :面试官很可能考察"为什么不能直接随机 r",一定要解释
sqrt的原因。 - 快排题 :可以顺带提"C++
std::sort通常是内省排序(introsort),结合快排、堆排和插入排序,避免最坏O(n²)"。
这些问题在游戏客户端面试中很常见,重点考察基础数据结构和概率分布的理解。