给出一个非抢占单线程CPU的 n 个函数运行日志,找到函数的独占时间。
每个函数都有一个唯一的 Id,从 0 到 n-1,函数可能会递归调用或者被其他函数调用。
日志是具有以下格式的字符串:function_id:start_or_end:timestamp
。例如:"0:start:0"
表示函数 0 从 0 时刻开始运行。"0:end:0"
表示函数 0 在 0 时刻结束。
函数的独占时间定义是在该方法中花费的时间,调用其他函数花费的时间不算该函数的独占时间。你需要根据函数的 Id 有序地返回每个函数的独占时间。
示例 1:
输入: n = 2 logs = ["0:start:0", "1:start:2", "1:end:5", "0:end:6"] 输出:[3, 4] 说明: 函数 0 在时刻 0 开始,在执行了 2个时间单位结束于时刻 1。 现在函数 0 调用函数 1,函数 1 在时刻 2 开始,执行 4 个时间单位后结束于时刻 5。 函数 0 再次在时刻 6 开始执行,并在时刻 6 结束运行,从而执行了 1 个时间单位。 所以函数 0 总共的执行了 2 +1 =3 个时间单位,函数 1 总共执行了 4 个时间单位。
说明:
- 输入的日志会根据时间戳排序,而不是根据日志Id排序。
- 你的输出会根据函数Id排序,也就意味着你的输出数组中序号为 0 的元素相当于函数 0 的执行时间。
- 两个函数不会在同时开始或结束。
- 函数允许被递归调用,直到运行结束。
- 1 <= n <= 100
思路:这道题用栈来做,栈里保存的是函数的id,规则如下:我们定义一个之前的时间戳preTime并初始化为0,每次我们遍历到一个新的字符串,就把对应度过的时间(当前时间time-之前的时间preTime)加到结果集res中(res初始化为全0(vector<int> res(n,0)),这里相当于是一段一段的加),更新preTime为time。然后判断类型,如果是start,就压入栈顶,如果是end就再把对应的id的时间加1,preTime也加1。这里对preTime的操作确实让人看着窒息,难以理解。我们换种思路来思考,既然要分解计算出函数的独立运行时间,那么我们就必须在函数之间插入阻断标志,比如题目给的示例:
["0:start:0",
"1:start:2",
"1:end:5",
"0:end:6"]
我们应该更新为如下形式:
["0:start:0",
"0:end:0",
"1:start:2",
"1:end:5",
"0:start:6",
"0:end:6"]
即任意两个函数之间的时间绝对不会有重叠,哪怕函数0是从6开始到6结束也绝对不会和函数1重叠(红色所示),那么我们计算每个函数的时间就很容易了,对于每个闭合区间(函数start-函数end),经过的时间等于(end-start+1)。所以对应到preTime为什么当类型为end时,preTime要加1且res[s.top()]+1,因为当前的字符串对应的操作是end,还属于当前函数的时间范围,所以对应的id的函数间隔要+1且时间preTime要往下移一位。
参考代码:
class Solution {
public:
vector<int> exclusiveTime(int n, vector<string>& logs) {
vector<int> res(n, 0);
stack<int> s;
int preTime = 0;
for (string log:logs) {
int found1 = log.find_first_of(":");
int found2 = log.find_last_of(":");
int idx = stoi(log.substr(0,found1));
string type = log.substr(found1 + 1, found2 - found1 - 1);
int time = stoi(log.substr(found2+1));
if (!s.empty()) {
res[s.top()] += time - preTime;
}
preTime = time;
if (type == "start") s.push(idx);
else {
int tmp = s.top(); s.pop();
++res[tmp];
preTime++;
}
}
return res;
}
};