1.lc636:函数的独占时间
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/exclusive-time-of-functions
题目:有一个 单线程 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 的函数的独占时间。
示例 :
输入: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 个单位时间。
思路:用栈模拟函数的调用,栈内存放的是函数的标识符和时间戳。如果是开始,就入栈,此时如果栈不为空,要计算原栈顶的执行时间;如果是结束,弹出栈顶,计算执行时间,此时如果栈不为空,更新栈顶的时间戳。
ps:一个小的注意点是计算问题(一直都很讨厌时间的计算)。由于开始是在时间戳的起始,结束是在时间戳的末尾,所以在结束的时候计算执行时间记得加一,以及栈顶的新时间戳也是结束时间戳加一。
代码:
class Solution {
public:
vector<int> exclusiveTime(int n, vector<string>& logs) {
vector<int>ans(n);
int index,time,len,i;
stack<pair<int,int>>st;
pair<int,int>pii;
bool isbegin;
for(string &s:logs){
len=s.length();
index=time=0;
for(i=0;i<len;i++){
if(s[i]==':')break;
index=index*10+s[i]-'0';
}
if(s[++i]=='s')isbegin=true;
else isbegin=false;
for(;i<len;i++){
if(s[i]==':')break;
}
for(i=i+1;i<len;i++){
time=time*10+s[i]-'0';
}
if(isbegin){
if(!st.empty()){
pii=st.top();
ans[pii.first]+=time-pii.second;
}
pii.first=index;
pii.second=time;
st.push(pii);
}
else{
pii=st.top();
st.pop();
ans[index]+=time-pii.second+1;//记得加一
if(!st.empty()){
pii=st.top();
st.pop();
pii.second=time+1;//这里也是
st.push(pii);
}
}
}
return ans;
}
};
补充:c++的string没有split函数(可恶),所以我就手动分割字符串了,后来在题解里看到了sscanf函数。
char type[10];
int idx, timestamp;
sscanf(log.c_str(), "%d:%[^:]:%d", &idx, type, ×tamp);
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/exclusive-time-of-functions/solution/han-shu-de-du-zhan-shi-jian-by-leetcode-d54e2/
来源:力扣(LeetCode)
2.lc768:最多能完成排序的块Ⅱ
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/max-chunks-to-make-sorted-ii
题目:这个问题和“最多能完成排序的块”相似,但给定数组中的元素可以重复,输入数组最大长度为2000,其中的元素最大为10**8。
arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
我们最多能将数组分成多少块?
示例 :
输入: arr = [5,4,3,2,1]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [5, 4], [3, 2, 1] 的结果是 [4, 5, 1, 2, 3],这不是有序的数组。
思路:【官方题解】
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/max-chunks-to-make-sorted-ii/solution/zui-duo-neng-wan-cheng-pai-xu-de-kuai-ii-w5c6/
来源:力扣(LeetCode)
对于已经分好块的数组,若块数大于 1,则可以得到以下结论:右边的块的所有数字均大于或等于左边的块的所有数字。考虑这个问题:对于已经分好块的数组,若在其末尾添加一个数字,如何求得新数组的分块方式?
新添加的数字可能会改变原数组的分块方式。如果新添加的数字大于或等于原数组最后一个块的最大值,则这个新添加的数字可以自己形成一个块。如果新添加的数字小于原数组最后一个块的最大值,则它必须融入最后一个块。如果它大于或等于原数组倒数第二个块(如果有)的最大值,那么这个过程可以停止,新数组的分块方式已经求得。否则,它将继续融合原数组倒数第二个块,直到遇到一个块,使得该块的最大值小于或等于这个新添加的数,或者这个数字已经融合了所有块。
上述分析过程中,我们只用到了块的最大值来进行比较,比较过程又是从右到左,符合栈的思想,因此可以用类似单调栈的数据结构来存储块的最大值。
代码:
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
stack<int> st;
for (auto &num : arr) {
if (st.empty() || num >= st.top()) {
st.emplace(num);
} else {
int mx = st.top();
st.pop();
while (!st.empty() && st.top() > num) {
st.pop();
}
st.emplace(mx);
}
}
return st.size();
}
};
ps:该题的哈希表做法请见做题记录——哈希表_Kephenny的博客-CSDN博客