Medium
题目描述
在单线程 CPU 上,我们执行一个包含 n 个函数的程序。每个函数都有一个 0 到 n-1 之间的唯一 ID。
函数调用存储在调用栈中:当函数调用开始时,其 ID 被推入栈中,当函数调用结束时,其 ID 从栈中弹出。栈顶的函数 ID 是当前正在执行的函数。每当函数开始或结束时,我们都会写一个包含 ID、开始或结束状态以及时间戳的日志。
给你一个日志列表 logs,其中 logs[i] 表示第 i 条日志消息,格式为字符串 “{function_id}:{“start” | “end”}:{timestamp}"。例如,“0:start:3” 表示函数 ID 为 0 的函数调用在时间戳 3 的开始时刻开始,“1:end:2” 表示函数 ID 为 1 的函数调用在时间戳 2 的结束时刻结束。注意,函数可能会被多次调用,甚至递归调用。
函数的独占时间是程序中该函数所有调用的执行时间总和。例如,如果一个函数被调用两次,一次执行 2 个时间单位,另一次执行 1 个时间单位,则独占时间为 2 + 1 = 3。
返回每个函数的独占时间数组,其中第 i 个索引处的值表示 ID 为 i 的函数的独占时间。
示例 1:
输入:n = 2, logs = ["0:start:0","1:start:2","1:end:5","0:end:6"]
输出:[3,4]
解释:
函数 0 在时间 0 开始执行,执行 2 个时间单位,到时间 1 结束。
函数 1 在时间 2 开始执行,执行 4 个时间单位,在时间 5 结束。
函数 0 在时间 6 恢复执行,执行 1 个时间单位。
所以函数 0 总共执行了 2 + 1 = 3 个时间单位,函数 1 执行了 4 个时间单位。
示例 2:
输入:n = 1, logs = ["0:start:0","0:start:2","0:end:5","0:start:6","0:end:6","0:end:7"]
输出:[8]
示例 3:
输入:n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:6","1:end:6","0:end:7"]
输出:[7,1]
提示:
- 1 <= n <= 100
- 2 <= logs.length <= 500
- 0 <= function_id < n
- 0 <= timestamp <= 10^9
- 不会有两个开始事件在同一时间戳发生
- 不会有两个结束事件在同一时间戳发生
- 每个函数的每个"start"日志都有对应的"end"日志
解题思路
这道题需要模拟函数调用栈的执行过程,计算每个函数的独占执行时间。
核心思路:
- 使用栈来模拟函数调用栈,栈中存储函数ID
- 遍历日志,根据start/end事件更新函数执行时间
- 关键在于理解"独占时间"的含义:函数执行时间 = 总时间 - 被其他函数占用的时间
具体算法:
- 维护一个栈存储正在执行的函数ID
- 对于start事件:
- 如果栈不为空,说明当前栈顶函数被暂停,需要累加其执行时间
- 将新函数ID入栈,记录开始时间
- 对于end事件:
- 计算当前函数的执行时间并累加
- 将函数ID出栈
- 更新时间戳为下一个时间点(因为end表示在该时间戳结束)
时间复杂度分析:
- 需要遍历所有日志,每个日志的处理时间为O(1)
- 解析字符串的时间也是常数时间
- 总时间复杂度为O(m),其中m是日志数量
空间复杂度:
- 栈的最大深度不会超过n(函数数量)
- 结果数组需要O(n)空间
- 总空间复杂度为O(n)
代码实现
class Solution {
public:
vector<int> exclusiveTime(int n, vector<string>& logs) {
vector<int> result(n, 0);
stack<int> st;
int prevTime = 0;
for (const string& log : logs) {
int colon1 = log.find(':');
int colon2 = log.find(':', colon1 + 1);
int funcId = stoi(log.substr(0, colon1));
string action = log.substr(colon1 + 1, colon2 - colon1 - 1);
int timestamp = stoi(log.substr(colon2 + 1));
if (action == "start") {
if (!st.empty()) {
result[st.top()] += timestamp - prevTime;
}
st.push(funcId);
prevTime = timestamp;
} else {
result[st.top()] += timestamp - prevTime + 1;
st.pop();
prevTime = timestamp + 1;
}
}
return result;
}
};
class Solution:
def exclusiveTime(self, n: int, logs: List[str]) -> List[int]:
result = [0] * n
stack = []
prev_time = 0
for log in logs:
parts = log.split(':')
func_id = int(parts[0])
action = parts[1]
timestamp = int(parts[2])
if action == "start":
if stack:
result[stack[-1]] += timestamp - prev_time
stack.append(func_id)
prev_time = timestamp
else:
result[stack[-1]] += timestamp - prev_time + 1
stack.pop()
prev_time = timestamp + 1
return result
public class Solution {
public int[] ExclusiveTime(int n, IList<string> logs) {
int[] result = new int[n];
Stack<int> stack = new Stack<int>();
int prevTime = 0;
foreach (string log in logs) {
string[] parts = log.Split(':');
int funcId = int.Parse(parts[0]);
string action = parts[1];
int timestamp = int.Parse(parts[2]);
if (action == "start") {
if (stack.Count > 0) {
result[stack.Peek()] += timestamp - prevTime;
}
stack.Push(funcId);
prevTime = timestamp;
} else {
result[stack.Peek()] += timestamp - prevTime + 1;
stack.Pop();
prevTime = timestamp + 1;
}
}
return result;
}
}
var exclusiveTime = function(n, logs) {
const result = new Array(n).fill(0);
const stack = [];
let prevTime = 0;
for (const log of logs) {
const [id, action, time] = log.split(':');
const funcId = parseInt(id);
const timestamp = parseInt(time);
if (action === 'start') {
if (stack.length > 0) {
result[stack[stack.length - 1]] += timestamp - prevTime;
}
stack.push(funcId);
prevTime = timestamp;
} else {
result[stack.pop()] += timestamp - prevTime + 1;
prevTime = timestamp + 1;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(m),其中 m 是日志数量,需要遍历所有日志 |
| 空间复杂度 | O(n),其中 n 是函数数量,栈的最大深度为 n,结果数组大小为 n |