Bracket Sequencing
编号:0013
题目来源:AtCoder
题目描述
给出
N
N
N个字符串,全都是由(
和)
组成的,判断
S
1
,
S
2
,
⋯
,
S
N
S_1,S_2,\cdots,S_N
S1,S2,⋯,SN能否以合理的顺序,满足其中的(
和)
配对成功。(注意配对的(
和)
一定是(
在前,)
在后)
变量范围:
- 1 ≤ N ≤ 1 0 6 1\leq N \leq 10^6 1≤N≤106
- S i S_i Si的长度最多为 1 0 6 10^6 106
- S i S_i Si非空数组,且仅包含
(
和)
输入
N
S 1 S_1 S1
S 2 S_2 S2
⋮ \vdots ⋮
S N S_N SN
输出
如果可以排列成满足条件的序列,输出Yes
,反之输出No
解答算法
首先可以将字符转化为数字方便统计,可以设(
为1,那么因为)
和(
相配对,所以设)
为-1。那么对于任何一个字符串,有意义的量有两个,一个是totalNum
,也就是这个串中遍历完之后的数字和,若为负数,说明其中)
较多,反之说明(
较多;另一个是minNum
,说明到从开始到任意字符
S
[
i
]
S[i]
S[i]为止的最小和。比如说(()(
的totalNum=2
,minNum=1
.
显然,要想满足题目中的配对要求,那么也就是说从头开始,到任意字符的数字和要为非负数,而且最终遍历完所有的字符串,要求数字和必须为0。
假设TotalNum
是遍历到字符串
S
i
S_i
Si之前的数字和,那么要求TotalNum+minNum >= 0
,满足条件就更新TotalNum
,新的TotalNum=TotalNum+totalNum
。
那么如何排序字符串 S i S_i Si,保证尽可能满足这个条件呢?
显然可以按照minNum
从大到小进行排列(totalNum>0
),但是totalNum >0
和totalNum<0
显然是两种情况,应该分开来讨论,所以说设置两个数组v1,v2
一个用来存储totalNum>0
,一个用来存储totalNum <0
,那么最终两个数组v1和v2
的TotalNum
应该互为相反数。
代码实现
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> P; //定义pair类型,first存放minNUm,second存放totalNum
const ll INF = 1e18;
#define rep(i, n) for (ll i = 0; i < (int)(n); i++)
int main() {
ll n;
cin >> n;
vector<P> v1;
vector<P> v2;
rep(i, n) { //对每个字符串S更新first,second
string s;
cin >> s;
ll m2 = 0; ll m1 = INF;
ll now = 0;
rep(j, s.size()) {
if (s[j] == '(') {
now += 1;
}
if (s[j] == ')') {
now -= 1;
}
m1 = min(m1, now); //更新minNum值
}
if (now > 0)v1.push_back(make_pair(m1, now)); //根据totalNum的正负进行分组
else v2.push_back(make_pair(m1 - now, -now)); //这里为了方便,对小于0的进行了反转,让他们变成大于零的数字进行统计
}
sort(v1.begin(), v1.end()); //对两数组进行排序,默认是升序
sort(v2.begin(), v2.end());
reverse(v1.begin(), v1.end()); //改为降序
reverse(v2.begin(), v2.end());
string ans = "Yes";
ll now = 0;
rep(i, v1.size()) { //如果任意字符串会有TotalNum<0,则失败
if (now + v1[i].first < 0) {
ans = "No"; break;
}
now += v1[i].second;
}
ll now1 = 0;
rep(i, v2.size()) { //同样如此
if (now1 + v2[i].first < 0) {
ans = "No";
break;
}
now1 += v2[i].second;
}
if (now1 != now)ans = "No"; //如果前后残余不一样,也失败
cout << ans;
}
复杂度分析
- 时间复杂度:整个过程进行了两次排序,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),对数组进行遍历,时间复杂度 O ( n ) O(n) O(n),因此总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 空间复杂度: O ( n ) O(n) O(n)