• 【字符串】函数的独占时间 栈


    题目描述

    给你一个由日志组成的列表 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:3","1:end:5","0:end:6"]
    输出:[4,3]
    解释:
    函数 0 在时间戳 0 的起始开始执行,执行 3 个单位时间。 
    函数 1 在时间戳 3 的起始开始执行,执行 3 个单位时间。 
    函数 0 在时间戳 6 的开始恢复执行,执行 1 个单位时间。 
    所以函数 0 总共执行 3 + 1 = 4 个单位时间,函数 1 总共执行 3 个单位时间。 

    解题思路

    这里这道题使用栈(deque)来解决:

    1. 准备一个方法专门解析日志文件。
    2. 针对数据特点,start->end->start->end 或者 start->start->end->end,每次遇到start则将数据压栈,deque.push
    3. 如果遇到end则出栈,并计算耗时;由于可能存在嵌套,那么再判断一下栈中是否有start数据,如果有则将刚刚计算出来的时间作为额外时间记录在栈顶元素中。

    根据上述思路,可以得到下面代码:

    1. import java.util.Arrays;
    2. import java.util.Deque;
    3. import java.util.LinkedList;
    4. import java.util.List;
    5. class Solution1 {
    6. public int[] exclusiveTime(int n, List logs) {
    7. int[] res = new int[n];
    8. Deque deque = new LinkedList<>();
    9. for (String log : logs) {
    10. Log obj = parse(log);
    11. if (obj.start) {
    12. deque.push(obj);
    13. } else {
    14. Log start = deque.pop();
    15. int value = obj.timestamp - start.timestamp + 1;
    16. if (deque.peek() != null) {
    17. deque.peek().innerDiff += value;
    18. }
    19. res[obj.functionId] += (value - start.innerDiff);
    20. }
    21. }
    22. return res;
    23. }
    24. private Log parse(String log) {
    25. String[] array = log.split(":");
    26. return new Log(Integer.valueOf(array[0]), "start".equals(array[1]), Integer.valueOf(array[2]));
    27. }
    28. class Log {
    29. int functionId;
    30. boolean start;
    31. int timestamp;
    32. int innerDiff;
    33. public Log(int functionId, boolean start, int timestamp) {
    34. this.functionId = functionId;
    35. this.start = start;
    36. this.timestamp = timestamp;
    37. this.innerDiff = 0;
    38. }
    39. }
    40. public static void main(String[] args) {
    41. Solution1 solution = new Solution1();
    42. System.out.println(Arrays.toString(solution.exclusiveTime(2, Arrays.asList(new String[]{"0:start:0", "1:start:2", "1:end:5", "0:end:6"}))));
    43. }
    44. }

    下面对代码耗时进行部分优化,我自己实现了一个栈,并且优化了解析逻辑,尽量降低处理的时间成本,目前发现自己实现的栈基本对耗时没有影响,优化解析逻辑反而带来明显的效果。下面是部分改造的代码:

    实现栈:

    class MyDeque {
    
        Log[] array;
        int tail;
    
        MyDeque(int length) {
            this.array = new Log[length / 2 + 1];
            this.tail = 1;
        }
    
        public void push(Log log) {
            array[tail++] = log;
        }
    
        public Log pop() {
            return array[--tail];
        }
    
        public Log peek() {
            return array[tail - 1];
        }
    }

    优化解析逻辑: 

    private Log parse(String log) {
    
        int functionId = 0;
        int timestamp = 0;
        boolean start = false;
        int i = 0;
        while (log.charAt(i) != ':') {
            functionId *= 10;
            functionId += (log.charAt(i) - '0');
            i++;
        }
        if (log.charAt(++i) == 's') {
            start = true;
            i += 6;
        } else {
            i += 4;
        }
        while (i < log.length()) {
            timestamp *= 10;
            timestamp += (log.charAt(i) - '0');
            i++;
        }
    
        return new Log(functionId, start, timestamp);
    }

    优化效果截图:

     

     

    下面是优化后的代码实现:

    1. import java.util.*;
    2. class Solution {
    3. public int[] exclusiveTime(int n, List logs) {
    4. int[] res = new int[n];
    5. MyDeque deque = new MyDeque(logs.size());
    6. for (String log : logs) {
    7. Log obj = parse(log);
    8. if (obj.start) {
    9. deque.push(obj);
    10. } else {
    11. Log start = deque.pop();
    12. int value = obj.timestamp - start.timestamp + 1;
    13. if (deque.peek() != null) {
    14. deque.peek().innerDiff += value;
    15. }
    16. res[obj.functionId] += (value - start.innerDiff);
    17. }
    18. }
    19. return res;
    20. }
    21. private Log parse(String log) {
    22. int functionId = 0;
    23. int timestamp = 0;
    24. boolean start = false;
    25. int i = 0;
    26. while (log.charAt(i) != ':') {
    27. functionId *= 10;
    28. functionId += (log.charAt(i) - '0');
    29. i++;
    30. }
    31. if (log.charAt(++i) == 's') {
    32. start = true;
    33. i += 6;
    34. } else {
    35. i += 4;
    36. }
    37. while (i < log.length()) {
    38. timestamp *= 10;
    39. timestamp += (log.charAt(i) - '0');
    40. i++;
    41. }
    42. return new Log(functionId, start, timestamp);
    43. }
    44. class Log {
    45. int functionId;
    46. boolean start;
    47. int timestamp;
    48. int innerDiff;
    49. public Log(int functionId, boolean start, int timestamp) {
    50. this.functionId = functionId;
    51. this.start = start;
    52. this.timestamp = timestamp;
    53. this.innerDiff = 0;
    54. }
    55. }
    56. class MyDeque {
    57. Log[] array;
    58. int tail;
    59. MyDeque(int length) {
    60. this.array = new Log[length / 2 + 1];
    61. this.tail = 1;
    62. }
    63. public void push(Log log) {
    64. array[tail++] = log;
    65. }
    66. public Log pop() {
    67. return array[--tail];
    68. }
    69. public Log peek() {
    70. return array[tail - 1];
    71. }
    72. }
    73. public static void main(String[] args) {
    74. Solution solution = new Solution();
    75. System.out.println(Arrays.toString(solution.exclusiveTime(2, Arrays.asList(new String[]{"0:start:0", "1:start:2", "1:end:5", "0:end:6"}))));
    76. }
    77. }

     总结

    这次优化发现即使自己定制实现一套栈,但是耗时也不一定是最优的(因为这个操作不是最大的短板)。

    反而是通过对常用字符串解析操作,做了一次优化,性能得到大幅度提升;但是,真实开发过程中,我还是会使用第一种实现方式,因为代码简单并且可读性强,后续维护成本也比较低。在代码性能、可读性、可维护性,我们其实需要做一个平衡的,这样可能才是实践要去决策的。

    最后性能优化还是要找到真正的短板,逐个优化才行,否则效果不会太明显的。

  • 相关阅读:
    JVM堆内存的结构,YGC,FGC的原理
    小白看了也会选:数据分析的常见工具有哪些
    01-Typescript基础
    SpringBoot+EasyExcel导入导出【加水印】
    1156 Sexy Primes – PAT甲级真题
    用 Python 展示全国高校的分布情况,你知道志愿该填哪了吗?
    qt多线程编程,信号绑定成功,槽函数不响应问题排查处理及总结
    1979-2018中国区域地面气象要素驱动数据日/月/年度合成产品
    用Python写一个去文档水印的算法
    手写call
  • 原文地址:https://blog.csdn.net/weiliuhong1/article/details/126209178