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



这是一个求解三角形相连集合的问题,可以采用图论中最大连通子图的思路来解决。算法步骤如下:

  1. 将索引数组转换为邻接表的形式,即将每个三角形看作一个节点,如果两个三角形有一条边共用,则它们之间存在一条无向边。
  2. 对邻接表进行深度优先搜索(DFS)或广度优先搜索(BFS),找到其中一个连通块,即为一个三角形相连集合。
  3. 重复步骤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:给出算法的平均时间复杂度(以注释的形式书写时间复杂度)

一个可能的解决方案是使用二分查找算法,以查找领奖所需的资源总量为目标,确定累计资源数量最早可达目标的时间点。
具体步骤如下:

  1. 如果res数组的最后一个元素都小于target,则无法领奖,返回-1.
  2. 使用二分查找算法,找到res中第一个大于等于target的元素的位置i
  3. 如果i等于0,则表示从第0小时开始就可以领奖,返回0
  4. 否则,返回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循环也可以找到最早时间点,但使用二分查找算法的平均时间复杂度明显更小。因此,在需要频繁进行查找的场景中,应该优先考虑使用二分查找等高效算法。
posted @ 2023-03-24 22:31  专心Coding的程侠  阅读(294)  评论(0编辑  收藏  举报