网友吐槽:大厂离职后,签好了竞业协议,天天发朋友圈在国外旅游,原公司HR立即让公司停止发放竞业补偿金,告知不竞业了!
刚看到个贴子:说有网友离职后签了竞业,结果天天发国外旅游照,原公司 HR 立马说那不竞业了,补偿金也停了。这事儿一下把大家都干懵了:口头说不算竞业,这到底靠不靠谱?
我觉得吧,问题根本不在旅游,而是在“边拿钱边炫生活”这事上。本质上,竞业补偿就是公司花钱买你一段时间的“不用怀疑你”的安心权,你一旦让公司觉得不确定了,人家当然有理由把钱停掉。
换个角度讲,大厂不是慈善机构,你拿着补偿金,又发一堆让人误会你去别处干活的朋友圈,这就跟拿着奶茶还怪店家为什么不让续杯一样,有点说不过去。
至于口头承诺靠不靠谱?嗯…反正我是不太信的,靠嘴说的都不稳,职场上没白纸黑字就是零。
面试题:任务调度器
先说下这个题在面试里怎么出现的哈:面试官一般会说,有一堆任务用字母标出来,比如 A A A B B B,机器一次只能执行一个任务,同一种任务之间必须“冷却” n 个时间单位,中间可以插别的任务或者发呆(idle)。问:最少需要多少个时间单位把所有任务做完。
经典例子:tasks = [A, A, A, B, B, B], n = 2,答案是 8,安排成:A B idle A B idle A B
1. 先用“排队”的直觉理解一下
你可以把每种任务的数量数一遍,比如:
A 出现了 3 次 B 出现了 3 次 其它字母可能 0 次
显然,数量最多的那几种任务,才是决定总时间的“老大哥”。 比如 A 和 B 都是 3 次,你再怎么穿插,A 之间至少要隔开 2 格,B 之间也要隔开 2 格。
一种比较好懂的想象方式:
把出现次数最多的任务先排成“骨架”: 对于 maxFreq = 3,先排:A _ _ A _ _ A这里一共有maxFreq - 1 = 2个“间隔块”,每块长度先看作n。然后把其它任务往这些空位里塞,能塞多少塞多少,塞不完的再额外往后排。
如果只有 A 一种任务,冷却时间 n=2,上面的骨架就是:
A _ _ A _ _ A一共是 (maxFreq - 1) * (n + 1) + 1 = (3 - 1) * 3 + 1 = 7 个时间。 再对照我们暴力排出来的 A B idle A B idle A B,是不是一个意思?
2. 数学一点的通式
我们把步骤稍微抽象一下:
统计每个任务的频次 freq[i]maxFreq = 所有 freq 里的最大值maxCount = 有多少种任务的频次等于 maxFreq
比如:
A:3, B:3, C:1, D:1那就是maxFreq = 3, maxCount = 2(A、B)
现在再画骨架的时候,不是只放 A,而是把所有最高频的任务都放在一列:
可以想象成每一层是一个“桶”:
A B
A B
A B
中间间隔块数:partCount = maxFreq - 1每个间隔块的“理论长度”:partLength = n - (maxCount - 1)
为啥是这个公式? 因为原来是每两个相同任务之间要空出 n 个“时间单位”, 但现在同一层有 maxCount 个最高频任务(A、B…),它们本身就会占掉一些冷却位置,所以实际还剩 n - (maxCount - 1) 个位置需要用“别的任务”或 idle 来填。
于是:
间隔块个数: partCount = maxFreq - 1每块需要补的位置数: partLength = Math.max(0, n - (maxCount - 1))总空位数: emptySlots = partCount * partLength其它任务数量: availableTasks = tasks.length - maxFreq * maxCount真正需要 idle 的数量: idles = Math.max(0, emptySlots - availableTasks)最终答案: tasks.length + idles
很多题解最后都会提一句: **结果是 Math.max(tasks.length, (maxFreq - 1) * (n + 1) + maxCount)**, 其实跟上面这套推导是等价的,只是写法略不一样,你理解任意一种就行。
3. 用 Java 写个完整方法
按照上面的思路,直接撸代码就比较顺了:
publicclassTaskScheduler{
// 主函数:返回最少需要的时间片数量
publicintleastInterval(char[] tasks, int n){
// 统计每个任务出现次数,题目里通常说任务是大写字母 A-Z
int[] freq = newint[26];
for (char c : tasks) {
freq[c - 'A']++;
}
// 找到最大频次 maxFreq
int maxFreq = 0;
for (int f : freq) {
if (f > maxFreq) {
maxFreq = f;
}
}
// 统计有多少种任务的频次等于 maxFreq
int maxCount = 0;
for (int f : freq) {
if (f == maxFreq) {
maxCount++;
}
}
// 间隔块数量
int partCount = maxFreq - 1;
// 每块理论上还需要多少冷却位
int partLength = n - (maxCount - 1);
if (partLength < 0) {
partLength = 0; // 说明最高频任务本身就把冷却填满了
}
// 总空位
int emptySlots = partCount * partLength;
// 除了最高频那几种任务以外,剩下的任务数
int availableTasks = tasks.length - maxFreq * maxCount;
// 还需要多少 idle
int idles = emptySlots - availableTasks;
if (idles < 0) {
idles = 0;
}
return tasks.length + idles;
}
// 简单测一下
publicstaticvoidmain(String[] args){
TaskScheduler ts = new TaskScheduler();
char[] tasks = {'A', 'A', 'A', 'B', 'B', 'B'};
int n = 2;
System.out.println(ts.leastInterval(tasks, n)); // 输出 8
}
}
这个解法的复杂度很友好:
统计次数 O(m),m 是任务数 后面几次循环都是固定 26 长度数组,O(1) 级别 总体就是 O(m) 时间、O(1) 额外空间
4. 常见坑顺便提醒一下
随便提几个写这题容易翻车的小点:
partLength可能为负 比如n很小,但最高频任务很多种,互相之间已经自然帮你把冷却填满了,这时候n - (maxCount - 1)会小于 0,一定要max(0, …)一下。idles也可能为负 说明其它任务已经把空位都塞满了,甚至还溢出了,这时候其实不需要 idle 了,结果就是tasks.length。不要真的去“模拟时间轴” 用优先队列一格一格去模拟也能做出来,但实现会比较长,而且面试官通常更想看你能不能抽象成上面的数学模型。
理解到这儿,这道“任务调度器”基本就拿捏住了,后面再遇到类似“有冷却时间”的排队类题目,也可以往“最高频任务 + 骨架 + 填空位”这个套路上去想。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html