1. 题目解析与需求理解
这道题目来自LeetCode的3528号问题"单位转换 I",属于图论与数学运算结合的算法题。题目要求我们实现一个单位转换系统,能够根据给定的单位转换关系,计算出所有单位与基准单位之间的换算系数。
核心需求可以分解为:
- 输入是一组三元组
[from, to, factor],表示1个from单位等于factor个to单位 - 需要建立一个完整的单位转换关系网
- 输出一个数组,其中每个元素表示对应单位与基准单位(0号单位)的转换系数
- 所有运算需要对10^9+7取模
注意:题目中的单位转换关系可能存在环路和冗余路径,算法需要正确处理这些情况。
2. 算法选择与思路分析
2.1 问题建模
这个问题可以抽象为图论中的有向图问题:
- 每个单位是一个节点
- 每个转换关系
[from, to, factor]是一条从from指向to的有向边,权重为factor - 我们需要从基准单位(0号节点)出发,计算到所有其他节点的"累积转换系数"
2.2 算法选择
考虑到单位转换关系的传递性,我们有以下几种算法选择:
-
深度优先搜索(DFS):
- 优点:实现简单,适合树状结构
- 缺点:可能陷入环路,需要额外处理环检测
-
广度优先搜索(BFS):
- 优点:天然避免重复访问,适合寻找最短路径
- 缺点:需要维护访问状态
-
Floyd-Warshall算法:
- 优点:可以处理所有节点对的最短路径
- 缺点:时间复杂度较高(O(n^3))
经过权衡,BFS是最适合本题的算法,因为:
- 我们只需要从基准单位出发的单源路径
- BFS的队列特性天然避免了重复计算
- 时间复杂度为O(V+E),在本题约束下效率足够
2.3 数学处理
单位转换本质上是乘法运算的累积。例如:
- 如果1 A = 2 B
- 且1 B = 3 C
- 那么1 A = 2 * 3 = 6 C
由于题目要求对10^9+7取模,我们需要在每次乘法后立即取模,防止整数溢出。
3. 代码实现与解析
3.1 数据结构设计
cpp复制class Solution {
public:
vector<int> baseUnitConversions(vector<vector<int>>& conversions) {
// 初始化结果数组,基准单位(0号)的系数为1
vector<int> ans(conversions.size() + 1, 0);
ans[0] = 1;
// 使用嵌套map存储图的邻接表
map<int, map<int, int>> visit;
// 构建邻接表
for (auto &index : conversions) {
visit[index[0]][index[1]] = index[2];
}
const long mod = 1000000000 + 7;
queue<int> Q;
Q.push(0); // 从基准单位开始BFS
while (!Q.empty()){
auto &index = Q.front();
for (auto &it : visit[index]) {
// 计算转换系数:ans[to] = ans[from] * factor
ans[it.first] = (ans[index] % mod) * (it.second % mod) % mod;
Q.push(it.first);
}
Q.pop();
}
return ans;
}
};
3.2 关键代码解析
-
邻接表构建:
cpp复制map<int, map<int, int>> visit; for (auto &index : conversions) { visit[index[0]][index[1]] = index[2]; }使用嵌套map存储图的邻接表,外层map的key是源节点,内层map的key是目标节点,value是转换系数。
-
BFS核心逻辑:
cpp复制while (!Q.empty()){ auto &index = Q.front(); for (auto &it : visit[index]) { ans[it.first] = (ans[index] % mod) * (it.second % mod) % mod; Q.push(it.first); } Q.pop(); }从队列中取出节点,遍历其所有邻接节点,计算转换系数并入队。这里使用了模运算防止溢出。
3.3 复杂度分析
- 时间复杂度:O(V+E),其中V是单位数量,E是转换关系数量
- 空间复杂度:O(V+E),用于存储邻接表和结果数组
4. 边界条件与测试案例
4.1 典型测试案例
-
简单线性转换:
cpp复制{{0,1,2}, {1,2,3}} // 预期输出:[1,2,6] -
分支结构:
cpp复制{{0,1,2}, {0,2,3}, {1,3,4}, {2,3,5}} // 预期输出:[1,2,3,8] (路径0->1->3的系数是2*4=8) -
环路检测:
cpp复制{{0,1,2}, {1,0,3}} // 预期输出:[1,2,0] (环路不会影响正确结果)
4.2 特殊边界情况
-
空输入:
cpp复制{} // 预期输出:[1] -
单个单位:
cpp复制{{0,0,1}} // 预期输出:[1,0] -
大数运算:
cpp复制{{0,1,1000000000}, {1,2,1000000000}} // 需要验证模运算是否正确
5. 优化与扩展思考
5.1 算法优化
当前实现使用BFS,对于某些特殊情况可以优化:
- 提前终止:如果所有单位都已计算出转换系数,可以提前结束BFS
- 并行计算:对于大规模数据,可以考虑多线程BFS
5.2 功能扩展
- 反向转换:可以扩展支持反向单位转换(如kg到g和g到kg)
- 复合单位:支持如"km/h"这样的复合单位转换
- 单位验证:检测转换关系是否自洽(无矛盾)
5.3 工程实践建议
在实际工程实现中,可以考虑:
- 缓存机制:缓存常用转换结果,提高性能
- 增量更新:当新增转换关系时,只更新受影响的部分
- 错误处理:添加对无效转换关系的检测和处理
6. 常见问题与调试技巧
6.1 常见问题
-
整数溢出:
- 现象:大数运算结果不正确
- 解决:确保每次乘法后立即取模
-
环路处理:
- 现象:程序陷入无限循环
- 解决:BFS天然避免重复访问,无需额外处理
-
未连接单位:
- 现象:某些单位转换系数为0
- 解决:这是题目要求的正确行为,表示无法转换
6.2 调试技巧
-
打印中间结果:
cpp复制cout << "Converting " << index << " to " << it.first << " with factor " << it.second << endl; -
可视化转换图:
- 使用Graphviz等工具绘制转换关系图
- 帮助理解数据结构和算法流程
-
单元测试:
- 为各种边界情况编写测试用例
- 使用assert验证关键计算结果
7. 替代实现方案
7.1 DFS实现
cpp复制void dfs(int u, vector<int>& ans, map<int, map<int, int>>& graph, const long mod) {
for (auto& [v, factor] : graph[u]) {
if (ans[v] == 0) { // 避免重复计算
ans[v] = (ans[u] % mod) * (factor % mod) % mod;
dfs(v, ans, graph, mod);
}
}
}
vector<int> baseUnitConversions(vector<vector<int>>& conversions) {
vector<int> ans(conversions.size() + 1, 0);
ans[0] = 1;
map<int, map<int, int>> graph;
for (auto& c : conversions) {
graph[c[0]][c[1]] = c[2];
}
const long mod = 1e9 + 7;
dfs(0, ans, graph, mod);
return ans;
}
7.2 Floyd-Warshall实现
cpp复制vector<int> baseUnitConversions(vector<vector<int>>& conversions) {
const int n = conversions.size() + 1;
const long mod = 1e9 + 7;
vector<vector<long>> dist(n, vector<long>(n, 0));
for (int i = 0; i < n; ++i) dist[i][i] = 1;
for (auto& c : conversions) {
dist[c[0]][c[1]] = c[2];
}
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (dist[i][k] && dist[k][j]) {
dist[i][j] = (dist[i][k] * dist[k][j]) % mod;
}
}
}
}
vector<int> ans(n);
for (int i = 0; i < n; ++i) {
ans[i] = dist[0][i];
}
return ans;
}
8. 性能对比与选择建议
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| BFS | O(V+E) | O(V+E) | 单源最短路径,无负权边 |
| DFS | O(V+E) | O(V) | 需要递归实现,可能栈溢出 |
| Floyd-Warshall | O(V^3) | O(V^2) | 所有节点对的最短路径 |
对于本题推荐使用BFS实现,因为:
- 只需要单源路径
- 实现简单不易出错
- 效率在题目约束下足够
在实际面试中,可以首先提出BFS方案,然后讨论其他实现的可能性,展示全面的算法理解。