题目描述
有一个 单线程 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]
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ex… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
- 注意,是单线程cpu,一道程序开始执行时,如果有程序正在执行,会结束掉前一个程序,后面自动开始执行
- 根据题目,如果在前面函数还没结束时新的函数就开始调用时,这个函数一定遵从后开始的先结束的原则。
- 综上,与 “栈” 后进先出的原理相似,所以我们用栈的原理来解此题,详细步骤看代码注释
代码
**
* @param {number} n
* @param {string[]} logs
* @return {number[]}
*/
var exclusiveTime = function(n, logs) {
let result=new Array(n).fill(0);//创建一个有n个元素的数组,并初始化值为0来储存最终计算的独占时间结果,结果下标与 logs里的函数id对应
let stack=[];//用来储存进行中函数的栈
for(let i=0;i<logs.length;i++){
let el=logs[i].split(':');
let now={
id:Number(el[0]),//函数id
type:el[1],//类型start|end
time:Number(el[2]),//结算的时间戳,当节点类型为start,遇到与这个节点对应end节点时,用时间相减就是这次函数持续的独占时间
};
if(now.type==='start'){//如果是开始节点,无脑丢到栈的前面(后进要先出,从前面遍历)
stack.unshift(now);
}else if(now.type==='end'){//如果是结束节点,需要从栈里去寻找对应的开始节点,并为其结算独占时间
//如何找到对应的开始节点?根据思路中提到后进先出的原则,我们只需要从栈的末尾开始遍历,第一个同id的开始节点就是要找的结算开始节点
let reTime=null //记录是否找到
stack.forEach((node,index)=>{
if(node.id===now.id&&!reTime){//id相同且第一次找到的,就是我们要找的结算节点
reTime=now.time-node.time+1;
result[now.id]+=reTime;//结算累加,同类型的时间
}else if(reTime){
//除了给自己结算以外,因为结算外层函数执行时间时候,需要减去内层执行函数的独占时间,所以计算出结算出时间之后,把这个时间累加到所有外层节点的time上,当外层节点结算时就能一起减去这部分时间
node.time+=reTime;
}
})
if(reTime){
stack.shift()//记住结算后,一定要删除这个开始节点,从栈前面删
}
}
}
return result;
};