T1:4719. 商品种类
第一题,依旧水
利用了set的唯一性,由于题意说字符串不超过十个字符,就补全后加入set就好
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
unordered_set<string> st;
while(n -- )
{
string a, b;
cin >> a >> b;
while(a.size() < 10) a = a + ' ';
while(b.size() < 10) b = b + ' ';
st.insert(a + b);
}
cout << st.size() << endl;
return 0;
}
再来一个,STL真好用
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
set<pair<string, string>> s;
while(n -- )
{
string a, b;
cin >> a >> b;
s.insert({a, b});
}
cout << s.size() << endl;
return 0;
}
set判重yyds
T2:4720. 字符串
总感觉在哪见过,题目思路也比较简单
在这里用栈来写好一点,从左往右数,找到一个删一个
想起来了,有点类似于祖玛的游戏哈哈哈哈哈
在这里我其实想证明一下为什么题目中说的
不论按何种顺序删除相同连续字母对,最终得到的字符串都是一样的
我们用数学归纳法
当时,此时字符串都不能删,那么结果唯一确定,成立
设时成立,证明时也成立
我们证明用任意一种解决方式都和用栈的结构相同,
用栈的话第一个删除的就是最左边的一对
用任意方式的话就有就有几种情况:
1,和栈完全一样,这样栈得到的字符串和用任意方式的相同
2,和栈不一样,但有公用部分,删完还是一样
3,和栈不一样,无公用部分,删除后就变成个字符串,又时成立,结果还是一样
这证明还是比较难的。。。。。。。,看不懂的话记结论就行。
下面用看看栈的代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
string str;
cin >> str;
string res;
for(auto c: str)
if(res.size() && res.back() == c) res.pop_back();
else res += c;
cout << res << endl;
return 0;
}
T3:4721. 排队
这题其实和leetcode第90场双周赛T4做法类似,
我最后也写个解法
说回本题,题目本身意思很明确,关键在于解法:单调队列+二分
对于本题,我们要考虑的是对于数组每个元素,找到它右边最小的元素
这样我们就模拟一个情况:
假如我们的目标元素是,找到右边最小的元素
这时我们有 和 ,此时假如 那么 就没有存在的必要了,因为我们的目标是 找到右边最小的元素,所以在这里 就可以删除
删掉之后我们就可以发现,后面的数就具有单调递增的性质,即前面一个数大于后面一个数那么前面一个数就没有了。
之后在单调的数组中找到它右边最小的元素,二分就好。
最后就变成这样一个栈,栈顶元素最小,最后单调递增。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int w[N], stk[N];
int ans[N];
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++ ) scanf("%d", &w[i]);
int top = 0;//定义栈顶
for(int i = n - 1; i >= 0; i -- )//从后往前枚举
{
if(!top || w[i] <= w[stk[top]]) ans[i] = -1;
//如果后面没有最小值比栈顶元素小,就输出-1
//此时最小值是栈顶
else
{
int l = 1, r = top;
while(l < r)
{
int mid = l + r >> 1;
if(w[stk[mid]] < w[i]) r = mid;
//如果发现当前元素是大于中间元素的话,改变边界R
else l = mid + 1;
}
ans[i] = stk[r] - i - 1;
}
if(!top || w[i] < w[stk[top]]) stk[ ++ top] = i;
//如果发现栈顶元素是空的话,或者栈顶元素比自己大的时候,就更新一下栈
}
for(int i = 0; i < n; i ++ ) printf("%d ", ans[i]);
return 0;
}
贴一个自己理解
现在看看2454. 下一个更大元素 IV
对于这题,我们要找的是后面第二大的数。
求后面第一大的数,可以想到acwing中算基的单调栈的做法。
在这里我们换个思路来看看
咱们从大到小枚举,在遍历到每个数的时候,比它大的数字都被标记出来
我们利用一个set来找到第二大的下标,upper_bound一次,再++
class Solution {
public:
vector<int> secondGreaterElement(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, -1);
//答案先都赋为-1
vector<int> p;
for(int i = 0; i < n; i ++ ) p.push_back(i);
sort(p.begin(), p.end(), [&](int x, int y){return nums[x] > nums[y];});
//插入下标并按大小排序
set<int> S;
S.insert(n + 1), S.insert(n + 2);
//处理边界条件,在最右端插入两个位置
for(int i = 0; i < n; i ++ ){
int j = i + 1;
while(j < n && nums[p[j]] == nums[p[i]]) j ++ ;
//出现数值相同的情况需要统一处理,就会出现相同的数字,这时候就要跳过
for(int k = i; k < j; k ++ )
{
auto it = S.upper_bound(p[k]);//比当前下标大的第一个下标
++ it;
if(*it < n) res[p[k]] = nums[*it];
}
for(int k = i; k < j; k ++ )
S.insert(p[k]);
i = j - 1;
//这里下标更新要注意
}
return res;
}
};
单调栈其实也可以做
1,先使用单调栈s1找到每个元素后一个更大的数字,在此基础上,每当单调栈s1出栈时,将其放入另一个单调栈s2中,接着等待元素后第二大的数字。
2,用s2存弹出等待的数字,其出栈时第二大的元素就是 nums(i) ,然后更新s1,弹栈并逆序压入s2
3,最后将nums(i) 压入s1。
class Solution {
public:
vector<int> secondGreaterElement(vector<int>& nums) {
const int n = nums.size();
vector<int> ans(n, -1);
stack<int> s1, s2;
for (int i = 0; i < n; i++) {
while (!s2.empty() && nums[i] > nums[s2.top()]) {
ans[s2.top()] = nums[i];
s2.pop();
}
vector<int> t;
while (!s1.empty() && nums[i] > nums[s1.top()]) {
t.push_back(s1.top());
s1.pop();
}
s1.push(i);
for (int j = t.size() - 1; j >= 0; j--)
s2.push(t[j]);
}
return ans;
}
};