图解LeetCode——636. 函数的独占时间(难度:中等)

一、题目

有一个 单线程 CPU 正在运行一个含有 n 道函数的程序。每道函数都有一个位于 0n-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 的函数的独占时间。

二、示例

2.1> 示例 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.2> 示例 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 个单位时间。

2.3> 示例 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 个单位时间。

提示:

  • 1 <= n <= 100
  • 1 <= logs.length <= 500
  • 0 <= function_id < n
  • 0 <= timestamp <= 10^9
  • 两个开始/结束事件不会在同一时间戳发生
  • 每道函数都有一个对应 "start" 日志的 "end" 日志

三、解题思路

3.1> 思路1:堆栈 + current指针

通过题意,我们很容易想到java虚拟机中栈帧那一章节,针对不同方法的调用,都是通过调用方法进行入栈,执行完毕进行出栈的一系列操作。那么,既然我们想到了栈帧,那么,针对这道题的解题思路,也会很容易就联想到采用堆栈的方式进行逻辑控制和计算。

比如,我们通过入参的logs中,获得了方法a()开始执行,那么我们就将方法a()的start放入到堆栈中;那么此时方法b()也开始执行,那么同样的,我们再将方法b()的start放入到堆栈中。当此时方法b()执行完毕后,那么方法b()的start和方法b()的end配对成功,则将方法b()的start执行出栈操作。那么此时,堆栈中只剩下方法a()的start了,那么当方法a()执行完毕后,方法a()的start和方法a()的end配对成功,那么将方法a()的start执行出栈操作即可。此时堆栈中是空的了。也说明此时没有任何正在执行中的方法了。

既然算法设计的数据存储结构我们确定好了,那么如何进行方法执行时间的计算呢?其实,如果对于这种先调用方法a()的start,紧接着就调用了方法a()的end,这种情况来说,计算方法执行时间非常简单,即:end的时间戳减去start的时间戳再加1即可。但是,如果是方法嵌套调用,这个就会比较麻烦,比如:调用方法a()的start,调用方法b()的start,调用方法c()的start,调用方法c()的end,调用方法b()的end,调用方法a()的end。这么操作过程中,除了方法c()之外,其他方法调用耗时都不是end-start+1了。

那针对这种嵌套操作的调用关系,由于某些方法被中断暂停导致耗时不是连续的,而是割裂的。那么,如何计算其耗时呢?这里我们采用“今朝有酒今朝醉”的方式。什么意思呢?我们下面以一个例子作为切入点,来演示一下什么叫做今朝有酒今朝醉,这样会比纯文字叙述更容易让大家理解。

输入n=2,logs=["0:start:0","0:start:2","0:end:5","1:start:7","1:end:7","0:end:8"],下面就是其执行时间的图示:

从logs获得第一个指令日志“0:start:0”,由于是第一个指令,则放入到堆栈中。此时的current指针用于指向“未参加计算”的起始位置。具体操作如下所示:

获得第二个指令日志“0:start:2”,由于还是start指令,所以,将其放入到堆栈中。但是此时由于又执行了start操作,所以第一个指令其实是处于暂停状态了,由于我们采用“今朝有酒今朝醉”的方式。所以,计算一下暂停前执行的耗时,并维护到result结果数组中;第二个start指令的时间戳是2,所以第一个指令执行了2个时间单位然后被暂停了。此时result数组中,index=0的元素被赋值为2。由于current指针指向的是未参加计算的起始位置,所以current被赋值为2。具体操作如下所示:

获得第三个指令日志“0:end:5”,由于是end指令,所以,从堆栈中pop弹出的栈顶元素一定就是其start指令的匹配指令。既然指令已经配对了,那么就可以计算start指令与end指令消耗的时间单位了。由于start指令是“0:start:2”,所以指令消耗的时间是5-2+1等于4,将result数组index等于0的元素加4,即:2+4等于6,current同被赋值为6

当接收到指令“1:start:7”的时候,我们相当于又将“0:start:0”的方法执行了暂停,由于current=6,所以那么再执行暂停前“0:start:0”的方法运行了1个时间单位,更新result数组index=0处元素值加1,并将current赋值为7

当接收到“1:end:7”时,我们可以计算出函数1的耗时单位为1,那么更新result数组index=1处元素值为1,并将current赋值为8

最后,当我们接收到最后一个日志指令“0:end:8”时,它其实就是针对我们第一个日志指令“0:start:0”的结束标识。由于此时current=8,而end的时间单元也是8,所以执行时间为1单元,更新result数组index=0处的元素值加1,即总的加和过程为:2+4+1+1=8

通过对log指令日志的遍历计算,最终result数组中,指令0(index=0)耗时8个时间单元;指令1(index=1)耗时1个时间单元

四、代码实现

4.1> 实现1:堆栈 + current指针

class Solution {
    public int[] exclusiveTime(int n, List<String> logs) {
        int current = 0;
        int[] result = new int[n];
        Deque<Integer> stack = new ArrayDeque(); 
        for (String log : logs) {
            String[] logArray = log.split(":");
            int endTime = Integer.valueOf(logArray[2]);
            if (logArray[1].equals("start")) {
                if (!stack.isEmpty()) {
                    result[stack.peek()] += endTime - current; 
                    current = endTime ;
                } 
                stack.push(Integer.valueOf(logArray[0]));
            } else {
                result[stack.pop()] += endTime - current + 1;
                current = endTime + 1;
            }
        }
        return result;
    }
}

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(o)/ ~ 「干货分享,每天更新」

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容