QJ 笔试题算法部分
T1-3D模型三角形面片
题意3D模型文件中存储了三角形面片数据,由顶点数组和索引数组两部分构成。
例如:
顶点数组:[V{x0,y0,z0}, V{x1,y1,z1}, V{x2,y2,z2}...],每个元素是1个顶点坐标
索引数组:[(0,1,2), (0,2,3), (4.5,6)…]。每个元素是1个三角形的3个顶点的index
其中有1条公共边的2个三角形称为相连,例如 (0,1,2) 和 (0,2,3)
求解以下最大的三角形相连集合的问题
* 输入:索引数组、数组size;
* 输出:最大的三角形相连集合的size;
* 说明:索引数组中没有重复的、非法的三角形
样例
输入:[(0,1,2), (0,2,3), (4,5,6), (2,3,4)], 4
输出:3
解释:最大的相连三角形集合为[(0,1,2), (0,2,3), (2,3,4)],有3个三角
示例2
输入
[[0,1,2],[4,5,6],[2,3,4]] 3
输出
1
示例3
输入[[1,2,3],[3,4,5],[2,3,4]] 3
输出
3
这是一个求解三角形相连集合的问题,可以采用图论中最大连通子图的思路来解决。算法步骤如下:
- 将索引数组转换为邻接表的形式,即将每个三角形看作一个节点,如果两个三角形有一条边共用,则它们之间存在一条无向边。
- 对邻接表进行深度优先搜索(DFS)或广度优先搜索(BFS),找到其中一个连通块,即为一个三角形相连集合。
- 重复步骤2,直到所有的节点都被访问完毕,找到最大的三角形相连集合。
具体实现时,可以先将索引数组转换为邻接表,然后从第一个节点开始进行DFS或BFS搜索,并标记已访问的节点。搜索完成后,即可得到一个三角形相连集合。然后从未访问的节点中选择一个节点进行下一轮搜索,直到所有节点都被访问完毕,找到最大的三角形相连集合。在搜索过程中可以使用计数器记录每个连通块的大小,并选择最大的连通块作为最大的三角形相连集合。
算法的时间复杂度为O(N+M),其中N为三角形的数量,M为邻接表中的边数。
下面是相应的C++代码实现:
#include <iostream>
#include <vector>
#include <unordered_set>
#include <queue>
using namespace std;
struct Triangle {
int v[3];
Triangle(int a, int b, int c) {
v[0] = a; v[1] = b; v[2] = c;
}
};
vector<vector<int>> buildGraph(const vector<Triangle>& triangles) {
vector<vector<int>> graph(triangles.size() * 3);
unordered_set<int> edgeSet;
for (int i = 0; i < triangles.size(); i++) {
for (int j = 0; j < 3; j++) {
int u = triangles[i].v[j], v = triangles[i].v[(j + 1) % 3];
int key = u * triangles.size() + v;
if (u > v) key = v * triangles.size() + u;
if (edgeSet.find(key) == edgeSet.end()) {
graph[u].push_back(v * 3 + i);
graph[v].push_back(u * 3 + i);
edgeSet.insert(key);
}
}
}
return graph;
}
int bfs(const vector<vector<int>>& graph, unordered_set<int>& visited, int start) {
visited.insert(start);
queue<int> q;
q.push(start);
int cnt = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : graph[u]) {
if (visited.find(v) == visited.end()) {
visited.insert(v);
cnt++;
q.push(v / 3 * 3 + ((v % 3) + 2) % 3);
}
}
}
return cnt;
}
int maxTriangleSet(const vector<Triangle>& triangles) {
vector<vector<int>> graph = buildGraph(triangles);
unordered_set<int> visited;
int maxCnt = 0;
for (int i = 0; i < graph.size(); i++) {
if (visited.find(i) == visited.end()) {
int cnt = bfs(graph, visited, i);
maxCnt = max(maxCnt, cnt / 3);
}
}
return maxCnt;
}
int main() {
vector<Triangle> triangles{{0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {5, 6, 7}, {2, 5, 6},
{1, 2, 5}, {3, 4, 7}, {2, 3, 5}, {2, 5, 6}, {1, 4, 5}};
int maxCnt = maxTriangleSet(triangles);
cout << maxCnt << endl;
return 0;
}
这里使用vector和unordered_set来实现邻接表和visited集合,使用queue来进行BFS搜索。
关于题目的理解:
Q:”索引数组:[(0,1,2), (0,2,3), (4.5,6)…]。每个元素是1个三角形的3个顶点的index
其中有1条公共边的2个三角形称为相连,例如 (0,1,2) 和 (0,2,3) ”
这是什么意思, 怎么看出两个三角形相连的?
A:在题目描述中,索引数组表示一个3D模型中的三角形面片数据。每个元素都是一个三元组,表示一个三角形面片的三个顶点的索引,例如(0,1,2)表示三角形的三个顶点的索引分别为0、1、2。
如果两个三角形共用一条边(即这条边同时也是这两个三角形的一个顶点),则称这两个三角形是相邻的。
例如,若三角形(0,1,2)和三角形(0,2,3)都包含顶点0和顶点2,则它们是相邻的。
在题目中提到的“索引数组中没有重复的、非法的三角形”,也就是说每个三元组表示的三角形都是合法的,且没有重复的。
通常情况下,三角形面片数据中每个三角形都与邻近的三角形共用至少一条边。
根据这个特点,我们可以将相邻的三角形连接起来,形成一个三角形相连的图。
在这张图中,每个三角形都是一个节点,它们之间的相邻关系就是边。
然后,我们就可以基于图论中的算法来计算最大的三角形连通块。
题目中的index怎么解释 ?
A:题目中的index是三角形中每个顶点在顶点列表中的索引。例如,如果顶点列表为[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)],则三角形(0, 1, 2)表示一个以(0, 0, 0)、(1, 0, 0)、(0, 1, 0)这三个顶点为顶点的三角形。
由于三角形的数量是原始输入数据中的关键信息,因此索引数组中的元素通常是按照三角形的编号排列的。在这种情况下,可以通过数组下标来获取三角形中每个顶点在顶点列表中的索引。
对于三角形数组[(0, 1, 2), (1, 2, 3), (0, 3, 1)],可以通过三角形(1, 2, 3)中的元素1和2找到顶点列表中的第2个点和第3个点,即(1, 0, 0)和(0, 1, 0)。这是因为数组下标从0开始计数,所以实际上是第2个和第3个点。
领奖励
某游戏有个玩法会每小时产出相应的资源(>=O),玩家可以通过累积该资源换取一定的奖励。给定一个—维整数数组res,其中res们]表示从开始到i小时中累积的资源总量。
问题1:
给定领取奖励所需要的资源总量target,请计算可以领取该奖励的最早时间,如果无法领奖返回-1。
示例1:
输入: res = [1,4,6,6,11], target = 5输出:3
输出:-1
问题1:
问题2:给出算法的平均时间复杂度(以注释的形式书写时间复杂度)
一个可能的解决方案是使用二分查找算法,以查找领奖所需的资源总量为目标,确定累计资源数量最早可达目标的时间点。
具体步骤如下:
- 如果res数组的最后一个元素都小于target,则无法领奖,返回-1.
- 使用二分查找算法,找到res中第一个大于等于target的元素的位置i
- 如果i等于0,则表示从第0小时开始就可以领奖,返回0
- 否则,返回i,表示从第i小时开始可以领奖
算法的时间复杂度为 O(logN),其中 N 为 res 数组长度,由于是二分查找,每次查找可以将搜索范围减小一半,因此总共最多只需要进行log2(N)次查找。
代码示例:
public static int earliestTimeToGetReward(int[] res, int target) {
int left = 0, right = res.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (res[mid] >= target) {
if (mid == 0 || res[mid - 1] < target) {
return mid;
} else {
right = mid - 1;
}
} else {
left = mid + 1;
}
}
return -1;
}
时间复杂度:O(logN)
Q:如果是通过for循环 查找领取该奖励的最早时间的平均时间复杂度是多少?
A:如果使用for循环查找领取该奖励的最早时间,平均时间复杂度会因为具体的输入数据而有所不同。假设res数组的长度为N,则:
- 最好情况是第一个元素就满足条件,时间复杂度为O(1)
- 最坏情况是需要遍历整个res数组,时间复杂度为O(N)
- 平均情况下需要遍历一半的res数组,因此平均时间复杂度为O(N/2) = O(N)
这里需要注意,虽然for循环也可以找到最早时间点,但使用二分查找算法的平均时间复杂度明显更小。因此,在需要频繁进行查找的场景中,应该优先考虑使用二分查找等高效算法。