|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[636. 函数的独占时间](https://leetcode.cn/problems/exclusive-time-of-functions/solution/by-ac_oier-z3ed/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「模拟」、「栈」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +有一个单线程 `CPU` 正在运行一个含有 `n` 道函数的程序。每道函数都有一个位于 `0` 和 `n-1` 之间的唯一标识符。 |
| 10 | + |
| 11 | +函数调用 存储在一个调用栈上 :当一个函数调用开始时,它的标识符将会推入栈中。而当一个函数调用结束时,它的标识符将会从栈中弹出。标识符位于栈顶的函数是当前正在执行的函数。每当一个函数开始或者结束时,将会记录一条日志,包括函数标识符、是开始还是结束、以及相应的时间戳。 |
| 12 | + |
| 13 | +给你一个由日志组成的列表 `logs` ,其中 `logs[i]` 表示第 `i` 条日志消息,该消息是一个按 `"{function_id}:{"start" | "end"}:{timestamp}"` 进行格式化的字符串。例如,`"0:start:3"` 意味着标识符为 `0` 的函数调用在时间戳 `3` 的 起始开始执行 ;而 `"1:end:2"` 意味着标识符为 `1` 的函数调用在时间戳 `2` 的末尾结束执行。注意,函数可以调用多次,可能存在递归调用 。 |
| 14 | + |
| 15 | +函数的「独占时间」定义是在这个函数在程序所有函数调用中执行时间的总和,调用其他函数花费的时间不算该函数的独占时间。例如,如果一个函数被调用两次,一次调用执行 `2` 单位时间,另一次调用执行 `1` 单位时间,那么该函数的独占时间为 `2 + 1 = 3` 。 |
| 16 | + |
| 17 | +以数组形式返回每个函数的独占时间,其中第 `i` 个下标对应的值表示标识符 `i` 的函数的独占时间。 |
| 18 | + |
| 19 | +示例 1: |
| 20 | + |
| 21 | +``` |
| 22 | +输入:n = 2, logs = ["0:start:0","1:start:2","1:end:5","0:end:6"] |
| 23 | +
|
| 24 | +输出:[3,4] |
| 25 | +
|
| 26 | +解释: |
| 27 | +函数 0 在时间戳 0 的起始开始执行,执行 2 个单位时间,于时间戳 1 的末尾结束执行。 |
| 28 | +函数 1 在时间戳 2 的起始开始执行,执行 4 个单位时间,于时间戳 5 的末尾结束执行。 |
| 29 | +函数 0 在时间戳 6 的开始恢复执行,执行 1 个单位时间。 |
| 30 | +所以函数 0 总共执行 2 + 1 = 3 个单位时间,函数 1 总共执行 4 个单位时间。 |
| 31 | +``` |
| 32 | +示例 2: |
| 33 | +``` |
| 34 | +输入:n = 1, logs = ["0:start:0","0:start:2","0:end:5","0:start:6","0:end:6","0:end:7"] |
| 35 | +
|
| 36 | +输出:[8] |
| 37 | +
|
| 38 | +解释: |
| 39 | +函数 0 在时间戳 0 的起始开始执行,执行 2 个单位时间,并递归调用它自身。 |
| 40 | +函数 0(递归调用)在时间戳 2 的起始开始执行,执行 4 个单位时间。 |
| 41 | +函数 0(初始调用)恢复执行,并立刻再次调用它自身。 |
| 42 | +函数 0(第二次递归调用)在时间戳 6 的起始开始执行,执行 1 个单位时间。 |
| 43 | +函数 0(初始调用)在时间戳 7 的起始恢复执行,执行 1 个单位时间。 |
| 44 | +所以函数 0 总共执行 2 + 4 + 1 + 1 = 8 个单位时间。 |
| 45 | +``` |
| 46 | +示例 3: |
| 47 | +``` |
| 48 | +输入:n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:6","1:end:6","0:end:7"] |
| 49 | +
|
| 50 | +输出:[7,1] |
| 51 | +
|
| 52 | +解释: |
| 53 | +函数 0 在时间戳 0 的起始开始执行,执行 2 个单位时间,并递归调用它自身。 |
| 54 | +函数 0(递归调用)在时间戳 2 的起始开始执行,执行 4 个单位时间。 |
| 55 | +函数 0(初始调用)恢复执行,并立刻调用函数 1 。 |
| 56 | +函数 1在时间戳 6 的起始开始执行,执行 1 个单位时间,于时间戳 6 的末尾结束执行。 |
| 57 | +函数 0(初始调用)在时间戳 7 的起始恢复执行,执行 1 个单位时间,于时间戳 7 的末尾结束执行。 |
| 58 | +所以函数 0 总共执行 2 + 4 + 1 = 7 个单位时间,函数 1 总共执行 1 个单位时间。 |
| 59 | +``` |
| 60 | +示例 4: |
| 61 | +``` |
| 62 | +输入:n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:7","1:end:7","0:end:8"] |
| 63 | +
|
| 64 | +输出:[8,1] |
| 65 | +``` |
| 66 | +示例 5: |
| 67 | +``` |
| 68 | +输入:n = 1, logs = ["0:start:0","0:end:0"] |
| 69 | +
|
| 70 | +输出:[1] |
| 71 | +``` |
| 72 | + |
| 73 | +提示: |
| 74 | +* $1 <= n <= 100$ |
| 75 | +* $1 <= logs.length <= 500$ |
| 76 | +* $0 <= function_id < n$ |
| 77 | +* $0 <= timestamp <= 10^9$ |
| 78 | +* 两个开始事件不会在同一时间戳发生 |
| 79 | +* 两个结束事件不会在同一时间戳发生 |
| 80 | +* 每道函数都有一个对应 `"start"` 日志的 `"end"` 日志 |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +### 模拟 |
| 85 | + |
| 86 | +我们使用「栈」来模拟执行过程:当一个函数被调用(`op = start`)时,压入栈内,当函数调用完成(`op = end`)时,从栈顶弹出(此时栈顶元素必然是该结束函数的入栈记录),使用变量 `cur` 记录当前时间。 |
| 87 | + |
| 88 | +从前往后处理所有的 $log[i]$,根据 $log[i]$ 是属于函数调用还是函数结束进行分情况讨论: |
| 89 | + |
| 90 | +* 当 $log[i]$ 为函数调用:此时从该函数的调用发起时间 `ts` 到上一次记录的当前时间,都是前一函数的执行时间,因此可以将 `ts - cur` 累加到栈帧中的前一函数。即若栈不为空,则将该时间累加到栈顶对应的函数上,然后将 $log[i]$ 入栈,同时更新当前时间; |
| 91 | +* 当 $log[i]$ 为函数结束:此时栈顶元素必然是该函数的调用记录,此时 $log[i]$ 的结束时间与上一次记录的当前时间的时长 `ts - cur + 1`,必然是该函数的执行时间,将其累加到当前函数中,并更新当前时间。 |
| 92 | + |
| 93 | +Java 代码: |
| 94 | +```Java |
| 95 | +class Solution { |
| 96 | + public int[] exclusiveTime(int n, List<String> logs) { |
| 97 | + int[] ans = new int[n]; |
| 98 | + Deque<Integer> d = new ArrayDeque<>(); |
| 99 | + int cur = -1; |
| 100 | + for (String log : logs) { |
| 101 | + String[] ss = log.split(":"); |
| 102 | + int idx = Integer.parseInt(ss[0]), ts = Integer.parseInt(ss[2]); |
| 103 | + if (ss[1].equals("start")) { |
| 104 | + if (!d.isEmpty()) ans[d.peekLast()] += ts - cur; |
| 105 | + d.addLast(idx); |
| 106 | + cur = ts; |
| 107 | + } else { |
| 108 | + int func = d.pollLast(); |
| 109 | + ans[func] += ts - cur + 1; |
| 110 | + cur = ts + 1; |
| 111 | + } |
| 112 | + } |
| 113 | + return ans; |
| 114 | + } |
| 115 | +} |
| 116 | +``` |
| 117 | +TypeScript 代码: |
| 118 | +```TypeScript |
| 119 | +function exclusiveTime(n: number, logs: string[]): number[] { |
| 120 | + const ans = new Array<number>(n).fill(0) |
| 121 | + const stk = new Array<number>() |
| 122 | + let he = 0, ta = 0, cur = -1 |
| 123 | + for (let log of logs) { |
| 124 | + const ss = log.split(":") |
| 125 | + const idx = Number(ss[0]), ts = Number(ss[2]) |
| 126 | + if (ss[1] == "start") { |
| 127 | + if (he < ta) ans[stk[ta - 1]] += ts - cur |
| 128 | + stk[ta++] = idx |
| 129 | + cur = ts |
| 130 | + } else { |
| 131 | + const func = stk[--ta] |
| 132 | + ans[func] += ts - cur + 1 |
| 133 | + cur = ts + 1 |
| 134 | + } |
| 135 | + } |
| 136 | + return ans |
| 137 | +}; |
| 138 | +``` |
| 139 | +* 时间复杂度:$O(n)$ |
| 140 | +* 空间复杂度:$O(n)$ |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +### 最后 |
| 145 | + |
| 146 | +这是我们「刷穿 LeetCode」系列文章的第 `No.636` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 147 | + |
| 148 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 149 | + |
| 150 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 151 | + |
| 152 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 153 | + |
0 commit comments