【1】32. 最长有效括号
日期:11.26
2.类型:栈,字符串,动态规划
3.方法一:栈(一次题解)
使用栈存储索引,栈底始终保存上一个无效的右括号位置或初始的-1
遇到左括号,索引入栈
遇到右括号,弹出栈顶元素(表示匹配)
如果弹出后栈为空,说明当前右括号没有匹配,将其索引入栈作为新的基准
否则,计算当前有效括号长度并更新最大值
关键代码:
cpp
for(int i=0;i<s.length();i++){
if(s[i]=='('){
stk.push(i);
}else{
stk.pop();
if(stk.empty()){
// 如果栈为空,说明当前右括号没有匹配的左括号
// 将其索引入栈作为新的基准
stk.push(i);
}else{
// 栈非空,计算当前有效括号长度并更新最大值
maxans=max(maxans, i-stk.top());
}
}
}
return maxans;
4.方法二:动态规划(半解)
从前往后遍历字符串求解 dp 值,每两个字符检查一次:
s[i]=')' 且 s[i−1]='(',也就是字符串形如 "......()",可以推出:dp[i]=dp[i−2]+2
可以进行这样的转移,是因为结束部分的 "()" 是一个有效子字符串,并且将之前有效子字符串的长度增加了 2 。
s[i]=')' 且 s[i−1]=')',也就是字符串形如 "......))",可以推出:如果 s[i−dp[i−1]−1]='(',那么dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
关键代码:
cpp
for(int i=1;i<n;i++){
if(s[i]==')'){ // 只有以')'结尾的才可能是有效括号
// 情况1:形如"...()",即当前')'和前一个'('匹配
if(s[i - 1]=='('){
dp[i]=(i>=2?dp[i-2]:0)+2;
}
// 形如"...))",需要检查是否有嵌套的有效括号
else if(i-dp[i-1]>0&&s[i-dp[i-1]-1]=='('){
dp[i]=dp[i-1]+((i-dp[i-1])>=2?dp[i-dp[i-1]-2] : 0)+2;
}
maxans=max(maxans, dp[i]);
}
}
【2】224. 基本计算器
日期:11.27
2.类型:栈,字符串,数学
3.方法一:栈(官方题解)
使用栈来记录每层括号前的符号状态
将表达式看作一系列数字的和,每个数字有正负号
括号会改变括号内所有项的符号
cpp
while(i<n){
if(s[i]==' '){
i++;
}
// 遇到加号,当前符号等于栈顶符号
else if(s[i]=='+'){
sign=ops.top();
i++;
}
//遇到减号,当前符号等于栈顶符号的相反数
else if(s[i]=='-'){
sign=-ops.top();
i++;
}
// 遇到左括号,将当前符号入栈
else if(s[i]=='('){
ops.push(sign);
i++;
}
//遇到右括号,弹出栈顶符号
else if(s[i]==')'){
ops.pop();
i++;
}
// 遇到数字,解析数字并累加
else{
long num=0;
while(i<n&&s[i]>='0'&&s[i]<='9'){
num=num*10+s[i]-'0';
i++;
}
ret+=sign*num; // 将数字乘以当前符号后累加
}
}
日期:11.28
2.类型:贪心,数组
3.方法一:贪心(一次题解)
遍历数组,记录当前遍历过的最大值
当当前最大值等于当前索引时,说明可以在此处分割
因为数组是 [0, n-1] 的排列,排序后每个位置 i 的值应该是 i
关键代码:
cpp
for(int i=0;i<arr.size();i++){
m=max(m,arr[i]); // 更新当前遍历过的最大值
if(m==i){ // 当前最大值等于当前索引
res++; // 可以在此处分割一个块
}
}
日期:11.29
2.类型:栈,数组,哈希表,排序
3.方法一:排序 + 哈希表(半解)
对于数组的任意前缀 [0, i],如果这个前缀中各个元素出现的次数与排序后数组的相同前缀中各个元素出现的次数完全相同,那么这个前缀可以独立排序,形成一个块。
比较原数组和排序后的数组
使用哈希表记录两个数组元素的计数差
当计数差为0时,说明当前位置之前的部分可以构成一个块
关键代码:
cpp
for(int i=0;i<sortedArr.size();i++){
int x=arr[i];
int y=sortedArr[i];
// 更新计数:原数组元素加1,排序数组元素减1
cnt[x]++;
if(cnt[x]==0){
cnt.erase(x);
}
cnt[y]--;
if(cnt[y]==0){
cnt.erase(y);
}
// 如果哈希表为空,说明当前位置可以分割
if(cnt.size()==0){
res++;
}
}
【5】636. 函数的独占时间
日期:11.30
2.类型:栈,数组
3.方法一:栈(官方题解)
栈顶的元素为当前正在执行函数:
当函数调用开始时,如果当前有函数正在运行,则当前正在运行函数应当停止,此时计算其的执行时间,然后将调用函数入栈。
当函数调用结束时,将栈顶元素弹出,并计算相应的执行时间,如果此时栈顶有被暂停的函数,则开始运行该函数。
关键代码:
cpp
for(auto& log:logs){
char type[10];
int idx, timestamp; // 函数id和时间
// 解析日志字符串
sscanf(log.c_str(), "%d:%[^:]:%d", &idx, type, ×tamp);
if(type[0]=='s'){
if(!st.empty()){
// 栈非空,当前正在运行的函数被中断
// 计算栈顶函数从上次开始到现在的运行时间
res[st.top().first]+=timestamp-st.top().second;
// 更新栈顶函数的开始时间为当前时间(被中断的时刻)
st.top().second=timestamp;
}
st.emplace(idx, timestamp);
}else{ // 函数结束
auto t=st.top();
st.pop();
// 计算当前函数的运行时间
res[t.first]+=timestamp-t.second+1;
if (!st.empty()){
// 栈非空,恢复执行被中断的函数
// 更新被中断函数的开始时间为当前时间+1
st.top().second=timestamp+1;
}
}
}
【6】856. 括号的分数
日期:12.01
2.类型:栈,数组
3.方法一:栈(一次题解)
平衡字符串 s 看作是一个空字符串加上 s 本身,并且定义空字符串的分数为 0。使用栈 st 记录平衡字符串的分数,在开始之前要压入分数 0,表示空字符串的分数。
在遍历字符串 s 的过程中:
遇到左括号,需要计算该左括号内部的子平衡括号字符串 A 的分数,要先压入分数 0,表示 A 前面的空字符串的分数。
遇到右括号,说明该右括号内部的子平衡括号字符串 A 的分数已经计算出来了,将它弹出栈,并保存到变量 v 中。如果 v=0,那么说明子平衡括号字符串 A 是空串,(A) 的分数为 1,否则 (A) 的分数为 2v,然后将 (A) 的分数加到栈顶元素上。
关键代码:
cpp
stack<int> st;
st.push(0);
for(auto c:s){
if(c=='('){
st.push(0);
}else{
int v=st.top();
st.pop();
st.top()+=max(2*v,1); // 将分数加到上一层
}
}
4.方法二:计算最终分数和(半解)
根据题意,s 的分数与 1 分的 () 有关。对于每个 (),它的最终分数与它所处的深度有关,如果深度为 bal,那么它的最终分数为 2^bal 。
关键代码:
cpp
for(int i=0;i<n;i++){
// 更新括号平衡度:遇到'('深度+1,遇到')'深度-1
bal+=(s[i]=='('?1:-1);
// 只在遇到一个完整的"()"时计算分数
// 当前是')',且前一个字符是'('(即当前字符构成"()")
if(s[i]== ')' && s[i-1]=='('){
// 2的bal次方(通过位运算1 << bal实现)
res+=1<<bal;
}
}
日期:12.02
2.类型:栈,数组
3.方法一:栈(半解)
首先,找出解码字符串的长度。之后,逆向工作,跟踪 size:解析符号 S[0], S[1], ..., S[i] 后解码字符串的长度。
如果看到一个数字 S [i],则表示在解析 S [0],S [1],...,S [i-1] 之后解码字符串的大小将是 size / Integer(S[i])。 否则,将是 size - 1。
关键代码:
cpp
for(int i=0;i<N;++i){
if(isdigit(S[i]))
size *= S[i]-'0'; // 重复当前字符串,长度乘以数字
else
size++;
}
// 从后向前逆向查找第K个字符
for(int i=N-1;i>=0;--i){
// 如果当前字符串重复了d次,那么第K个字符对应于重复前字符串的第(K-1)%size+1个字符
K %= size;
if(K==0&&isalpha(S[i]))
return string(1, S[i]); // 返回单个字符的字符串
//回退一步
if(isdigit(S[i]))
size/=S[i]-'0';
else
size--;
}
【8】895. 最大频率栈
日期:12.03
2.类型:栈,哈希表
3.方法一:哈希表和栈(官方题解)
用一个哈希表 freq 来记录每个元素出现的次数。设当前最大频率为 maxFreq,为 1∼maxFreq 中的每种频率单独设置一个栈。为了方便描述,记 freq[x] 为 x 的频率,group[i] 为频率为 i 的栈。
当元素 x 入栈时,令 freq[x]+1,然后将 x 放入 group[freq[x]] 中,更新 maxFreq=max(maxFreq,freq[x])。此时,group[1]∼group[freq[x]] 的每一个栈中都包含 x。
元素出栈时,获取 x=group[maxFreq].top() 作为出栈元素,令 freq[x]−1,若 x 出栈后 group[maxFreq] 为空,则令 maxFreq−1。
关键代码:
cpp
void push(int val){
// 更新元素频率:当前频率+1
freq[val]++;
int currentFreq = freq[val];
// 同一频率的元素,后添加的会在栈顶
group[currentFreq].push(val)
//更新最大频率
maxFreq = max(maxFreq, currentFreq);
}
int pop(){
// 从最大频率对应的栈中取出栈顶元素
int val = group[maxFreq].top();
// 更新该元素的频率:减少1
freq[val]--;
// 从栈中移除该元素
group[maxFreq].pop();
// 如果最大频率的栈变为空,降低最大频率
if (group[maxFreq].empty()){
maxFreq--;
}
return val;
}
【9】901. 股票价格跨度
日期:12.04
2.类型:栈,设计
3.方法一:单调栈(一次题解)
单调性:栈中价格严格递减(栈底到栈顶)
跨度计算 :idx - stk.top().first 是正确的,因为:
栈顶是最近的价格高于当前价格的元素
所有在栈顶之后的价格都小于等于当前价格(已被弹出)
因此,从栈顶的索引+1到当前索引的所有价格都 ≤ 当前价格
关键代码:
cpp
int next(int price) {
idx++;
// 弹出所有价格小于等于当前价格的栈顶元素
while(price>=stk.top().second){
stk.pop();
}
// 计算跨度:当前索引 - 栈顶元素的索引
int ret = idx - stk.top().first;
// 将当前价格压入栈中
stk.emplace(idx, price);
return ret;
【10】921. 使括号有效的最少添加
日期:12.05
2.类型:贪心,字符串
3.方法一:贪心(一次题解)
两个需要添加括号的情况
右括号过多:当遇到右括号但没有左括号可以匹配时,需要添加左括号
左括号过多:遍历结束后,如果还有未匹配的左括号,需要添加右括号
cpp
for(auto &c:s){
if(c=='('){
// 增加未匹配的左括号计数
leftCount++;
}else{ // c == ')'
if(leftCount>0){
// 有未匹配的左括号可以匹配:减少计数
leftCount--;
}else{
// 没有左括号可以匹配:需要添加一个左括号
ans++;
}
}
}