记录2道算法题
函数的独占时间
函数独占时间是指每个函数,自己正在执行的时间。如果是嵌套的话,嵌套的函数执行的时间不算外部函数的独占时间。而是内部嵌套的函数自身的独占时间。
要求:
* 提供一个 logs 数组,里面的信息是 id:位置:时间。 0:start:3
* 计算出每个函数独占时间。保存成数组。顺序是函数开始调用的先后顺序。
* 如果是同一个函数,时间将叠加到一起。最后输出的是 某个函数调用的总时间。
当看到题目的时候,函数的执行调用栈。很明显是要用栈来解决问题。首先先提取信息。可以用split(':') 进行切割,得到各个数据。
然后是时间的计算,从给的例子来看,每 1 就是 1 个单位时间。 3 -> 4 为 2 秒,是占用了3秒,4秒,这两秒。但是有一个例外,就是嵌套调用的时候, 0:start:0,0:start: 4, 0花费的时间为 4。就是前一个函数执行了0秒 1秒 2秒 3秒,第4秒开始算嵌套函数的时间。所以得出的规律是,当嵌套函数时,执行时间为 后减前。当是一个函数的 start 和 end 的时候,执行时间为 end - start + 1
接下来就是如何保存时间。嵌套函数开始调用前的时间是存在的需要加上的。
用一个对象存放任务的信息,然后随着执行被推入栈中,等下一个推入的时候,根据下一个的时间和栈顶的任务时间的差值得出上一个函数的独占时间,并保存到上一个任务对象里。为了解决弹出的任务数据的存放问题。为了保证顺序。用另外一个数组存放对象。也利用了对象的特点,可以通过修改对象的内容来进行更新时间。
while(logs.length) {
const m = logs.shift()
let [id, status, _time] = m.split(':')
_time = +_time
// 任务数据里的时间是记录独占时间,不是开始时间。
const data = { id, status, time: 0 }
...do something
}
为了更好的记录时间,用一个变量 time ,因为时间是一直往前走的,所以用这个变量来记录上一次 log 的时间点,与这次循环的 log 时间点进行计算,就得出了上一个函数到现在的独占时间,再保存到上一个任务信息的 time 里面。
同时为了根据当前任务信息的状态是 start 或者 end,来处理当前是嵌套还是结束任务。
// 而上一个任务信息的状态永远是 start,因为 end 不会被推入栈中。
const prev = stack[stack.length - 1]
if (status === 'start') {
// 嵌套时间不需要 +1
prev.time = prev.time + _time - time
time = _time
stack.push(data)
} else {
// 任务结束
prev.time = prev.time + _time - time + 1
// 结束任务的 end 的时间也算是 函数的独占时间
// 即 3 -> 4,任务结束后 从第5秒开始为另一个函数的时间
time = _time + 1
stack.pop()
}
接下来只要把这些串起来,最后加上检查同 id 的任务,然后把时间汇总起来就完成了。
完整代码如下, 肯定不是唯一的解法,因为官方传的参数 n ,都没有用上。
function exclusiveTime(n, logs) {
const stack = []
const result = []
let time = 0
while (logs.length) {
const m = logs.shift()
let [id, status, _time] = m.split(':')
_time = +_time
const data = { id, status, time: 0 }
// 用来收集正确顺序的函数调用,内部的值由对象改变
if (status === 'start') {
result.push(data)
}
const prev = stack[stack.length - 1]
// 当第一次或者弹干净,栈都是空的。这时直接推入
if (!prev) {
stack.push(data)
time = _time
continue
}
if (status === 'start') {
prev.time = _time - time
time = _time
stack.push(data)
} else {
prev.time = prev.time + _time - time + 1
time = _time + 1
stack.pop()
}
}
// 记录次数,然后转成数组
const map = {}
for (let i = 0; i < result.length; i++) {
const m = result[i]
// map[m.id] 是 undefined 的时候,初始化为 m.time
map[m.id] = map[m.id] + m.time || m.time
}
return Object.values(map)
}
表现良好的最长时间段
这个问题属实有点难,题解一开始看着也很阴间,前缀和,单调栈,贪心。不太懂哦。
前缀和稍微简单一点,就是像斐波那契数列那样的,每一项等于前面的项和自己的和。
可以把 [1,2,3,4] 大于3 ,简化成 [-1, -1, -1, 1], 这样的数组,然后就是多个累加起来,大于0的区间就是有效区间。然后求的是最长的区间是多长。
而前缀和在这里起的作用是,假如 [a, b, c, d, e] 要算加起来大于0的最长区间。就是 a+b,a+b+c,a+b+c+d,....,d+e。等可能性中长度最长且大于0。而前缀和 [z, y, x, w, s] , z = a + b, y = a + b + c, x = a + b + c + d
当 x - z 的时候,算的是 (a + b + c + d) - (a + b), 是 c + d。
所以这就是前缀和妙的地方。其他等了解了再进行补充。
这道题的完整代码如下
function longestWPI(hours) {
// 生成 [1, -1] 数组
const arr = Array.from(hours, v => (v > 8 ? 1 : -1))
// 生成 前缀和数组
const prefix = arr.reduce(
(total, curr, index) => {
total.push(curr + total[index])
return total
},
[0]
)
let count = 0
let m = prefix.length
// 用一个双循环计算 前缀和的差,即大于0的最长区间
for (let i = 0; i < m; i++) {
for (let j = i + 1; j < m; j++) {
if (prefix[j] - prefix[i] > 0) {
count = Math.max(count, j - i)
}
}
}
return count
}
``