有一个 单线程 CPU 正在运行一个含有 n
道函数的程序。每道函数都有一个位于 0
和 n-1
之间的唯一标识符。
函数调用
存储在一个调用栈
上 :当一个函数调用开始时,它的标识符将会推入栈中。而当一个函数调用结束时,它的标识符将会从栈中弹出。标识符位于栈顶的函数是 当前正在执行的函数
。每当一个函数开始或者结束时,将会记录一条日志,包括函数标识符、是开始还是结束、以及相应的时间戳。
给你一个由日志组成的列表 logs
,其中 logs[i]
表示第 i
条日志消息,该消息是一个按
"{function_id}:{"start" | "end"}:{timestamp}"
进行格式化的字符串。例如,"0:start:3"
意味着标识符为 0
的函数调用在时间戳 3
的 起始开始执行 ;而 "1:end:2"
意味着标识符为 1
的函数调用在时间戳
2
的 末尾结束执行 。注意,函数可以 调用多次,可能存在递归调用 。
函数的 独占时间
定义是在这个函数在程序所有函数调用中执行时间的总和,调用其他函数花费的时间不算该函数的独占时间。例如,如果一个函数被调用两次,一次调用执行 2
单位时间,另一次调用执行 1
单位时间,那么该函数的 独占时间 为 2 + 1 = 3
。
以数组形式返回每个函数的 独占时间 ,其中第 i
个下标对应的值表示标识符 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]
**解释:**
函数 0 在时间戳 0 的起始开始执行,执行 2 个单位时间,并递归调用它自身。
函数 0(递归调用)在时间戳 2 的起始开始执行,执行 4 个单位时间。
函数 0(初始调用)恢复执行,并立刻再次调用它自身。
函数 0(第二次递归调用)在时间戳 6 的起始开始执行,执行 1 个单位时间。
函数 0(初始调用)在时间戳 7 的起始恢复执行,执行 1 个单位时间。
所以函数 0 总共执行 2 + 4 + 1 + 1 = 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]
**解释:**
函数 0 在时间戳 0 的起始开始执行,执行 2 个单位时间,并递归调用它自身。
函数 0(递归调用)在时间戳 2 的起始开始执行,执行 4 个单位时间。
函数 0(初始调用)恢复执行,并立刻调用函数 1 。
函数 1在时间戳 6 的起始开始执行,执行 1 个单位时间,于时间戳 6 的末尾结束执行。
函数 0(初始调用)在时间戳 7 的起始恢复执行,执行 1 个单位时间,于时间戳 7 的末尾结束执行。
所以函数 0 总共执行 2 + 4 + 1 = 7 个单位时间,函数 1 总共执行 1 个单位时间。
示例 4:
**输入:** n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:7","1:end:7","0:end:8"]
**输出:** [8,1]
示例 5:
**输入:** n = 1, logs = ["0:start:0","0:end:0"]
**输出:** [1]
提示:
1 <= n <= 100
1 <= logs.length <= 500
0 <= function_id < n
0 <= timestamp <= 109
- 两个开始事件不会在同一时间戳发生
- 两个结束事件不会在同一时间戳发生
- 每道函数都有一个对应
"start"
日志的 "end"
日志
方法一:栈
思路与算法
我们可以用栈来模拟函数调用的过程,栈顶的元素为当前正在执行函数:
- 当函数调用开始时,如果当前有函数正在运行,则当前正在运行函数应当停止,此时计算其的执行时间,然后将调用函数入栈。
- 当函数调用结束时,将栈顶元素弹出,并计算相应的执行时间,如果此时栈顶有被暂停的函数,则开始运行该函数。
由于每一个函数都有一个对应的 start 和 end 日志,且当遇到一个 end 日志时,栈顶元素一定为其对应的 start 日志。那么我们对于每一个函数记录它的函数标识符和上次开始运行的时间戳,此时我们只需要在每次函数暂停运行的时候来计算执行时间和开始运行的时候更新时间戳即可。
代码
[sol1-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution: def exclusiveTime(self, n: int, logs: List[str]) -> List[int]: ans = [0] * n st = [] for log in logs: idx, tp, timestamp = log.split(':') idx, timestamp = int(idx), int(timestamp) if tp[0] == 's': if st: ans[st[-1][0]] += timestamp - st[-1][1] st[-1][1] = timestamp st.append([idx, timestamp]) else: i, t = st.pop() ans[i] += timestamp - t + 1 if st: st[-1][1] = timestamp + 1 return ans
|
[sol1-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public: vector<int> exclusiveTime(int n, vector<string>& logs) { stack<pair<int, int>> st; vector<int> res(n, 0); for (auto& log : logs) { char type[10]; int idx, timestamp; sscanf(log.c_str(), "%d:%[^:]:%d", &idx, type, ×tamp); if (type[0] == 's') { if (!st.empty()) { res[st.top().first] += timestamp - st.top().second; st.top().second = timestamp; } st.emplace(idx, timestamp); } else { auto t = st.top(); st.pop(); res[t.first] += timestamp - t.second + 1; if (!st.empty()) { st.top().second = timestamp + 1; } } } return res; } };
|
[sol1-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Solution { public int[] exclusiveTime(int n, List<String> logs) { Deque<int[]> stack = new ArrayDeque<int[]>(); int[] res = new int[n]; for (String log : logs) { int idx = Integer.parseInt(log.substring(0, log.indexOf(':'))); String type = log.substring(log.indexOf(':') + 1, log.lastIndexOf(':')); int timestamp = Integer.parseInt(log.substring(log.lastIndexOf(':') + 1)); if ("start".equals(type)) { if (!stack.isEmpty()) { res[stack.peek()[0]] += timestamp - stack.peek()[1]; stack.peek()[1] = timestamp; } stack.push(new int[]{idx, timestamp}); } else { int[] t = stack.pop(); res[t[0]] += timestamp - t[1] + 1; if (!stack.isEmpty()) { stack.peek()[1] = timestamp + 1; } } } return res; } }
|
[sol1-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Solution { public int[] ExclusiveTime(int n, IList<string> logs) { Stack<int[]> stack = new Stack<int[]>(); int[] res = new int[n]; foreach (string log in logs) { int idx = int.Parse(log.Substring(0, log.IndexOf(':'))); string type = log.Substring(log.IndexOf(':') + 1, log.LastIndexOf(':') - log.IndexOf(':') - 1); int timestamp = int.Parse(log.Substring(log.LastIndexOf(':') + 1)); if ("start".Equals(type)) { if (stack.Count > 0) { res[stack.Peek()[0]] += timestamp - stack.Peek()[1]; stack.Peek()[1] = timestamp; } stack.Push(new int[]{idx, timestamp}); } else { int[] t = stack.Pop(); res[t[0]] += timestamp - t[1] + 1; if (stack.Count > 0) { stack.Peek()[1] = timestamp + 1; } } } return res; } }
|
[sol1-C]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| typedef struct { int idx; int timestamp; } Pair;
int* exclusiveTime(int n, char ** logs, int logsSize, int* returnSize) { Pair *stack = (Pair *)malloc(sizeof(Pair)* logsSize); int *res = (int *)malloc(sizeof(int) * n); memset(res, 0, sizeof(int) * n); int top = 0; for (int i = 0; i < logsSize; i++) { char type[10]; int idx, timestamp; sscanf(logs[i], "%d:%[^:]:%d", &idx, type, ×tamp); if (type[0] == 's') { if (top > 0) { res[stack[top - 1].idx] += timestamp - stack[top - 1].timestamp; stack[top - 1].timestamp = timestamp; } stack[top].idx = idx; stack[top].timestamp = timestamp; top++; } else { res[stack[top - 1].idx] += timestamp - stack[top - 1].timestamp + 1; top--; if (top > 0) { stack[top - 1].timestamp = timestamp + 1; } } } free(stack); *returnSize = n; return res; }
|
[sol1-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var exclusiveTime = function(n, logs) { const stack = []; const res = new Array(n).fill(0); for (const log of logs) { const idx = parseInt(log.substring(0, log.indexOf(':'))); const type = log.substring(log.indexOf(':') + 1, log.lastIndexOf(':')); const timestamp = parseInt(log.substring(log.lastIndexOf(':') + 1)); if ("start" === type) { if (stack.length) { res[stack[stack.length - 1][0]] += timestamp - stack[stack.length - 1][1]; stack[stack.length - 1][1] = timestamp; } stack.push([idx, timestamp]); } else { const t = stack.pop(); res[t[0]] += timestamp - t[1] + 1; if (stack.length) { stack[stack.length - 1][1] = timestamp + 1; } } } return res; };
|
[sol1-Golang]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func exclusiveTime(n int, logs []string) []int { ans := make([]int, n) type pair struct{ idx, timestamp int } st := []pair{} for _, log := range logs { sp := strings.Split(log, ":") idx, _ := strconv.Atoi(sp[0]) timestamp, _ := strconv.Atoi(sp[2]) if sp[1][0] == 's' { if len(st) > 0 { ans[st[len(st)-1].idx] += timestamp - st[len(st)-1].timestamp st[len(st)-1].timestamp = timestamp } st = append(st, pair{idx, timestamp}) } else { p := st[len(st)-1] st = st[:len(st)-1] ans[p.idx] += timestamp - p.timestamp + 1 if len(st) > 0 { st[len(st)-1].timestamp = timestamp + 1 } } } return ans }
|
复杂度分析
- 时间复杂度:O(n),其中 n 为全部日志 logs 的数量,n 条日志信息对应总共 n 次入栈和出栈操作。
- 空间复杂度:O(n),其中 n 为全部日志 logs 的数量,n 条日志信息对应 n}{2 次入栈操作,最坏的情况下全部 n}{2 条日志入栈后才会依次弹栈。