括号匹配问题:从不可替换括号方向到有限制替换括号方向
括号匹配总体思路
1、在有效括号字符串的任意一个前缀中,左括号的数量都大于等于右括号的数量。例如有效括号字符串 (())(),它的前缀有 ((),(())( 等,都满足这一性质。为什么?
因为对于有效括号字符串的前缀来说,每个右括号的左边,必然有与之匹配的左括号,但左括号不一定有与之匹配的右括号。
2、根据这一性质,从左到右遍历字符串 s,统计未匹配的左括号的个数 c:遇到左括号就把 c 加一,遇到右括号就把 c 减一(匹配一个左括号)。如果任何时刻 c 都不为负数,且最终 c=0,那么 s 就是有效括号字符串。
例如 s=(())(),遍历 s 的过程中,c 是这样变化的:1→2→1→0→1→0
3、如果遍历中途,发现一个),但此时c==0,说明这个右括号一定是非法的,他的左侧没有一个括号能与之匹配,如果题目限制不可以替换括号方向的话,此时就一定不是个合法的有效括号字符串。
问题 1:允许部分位置的括号方向可以变化
我们以 力扣2116. 判断一个括号字符串是否有效 为例对该问题进行求解
步骤 1 :分析与原版问题的差别
1、和普通版括号匹配相比,本题允许在一些特定位置,将括号的方向进行替换。这题本质上也还是个括号匹配问题,所以跳不出上面介绍的思路,我们现在重点的问题就是如何对它的特殊条件做出变化
2、当locked[i]=0,代表了i出括号的方向是未知的,可以左,也可以右,我们标记为“?”。此时很明显,左括号的数量c是一个不确定的值,由于?的存在,导致c的数量是一个变化的,取值范围在一个集合中。(以下图为例,图片链接)
3、我们发现一个?,就代表着它可以是左括号或者右括号,那么c的数量+1或者-1;并且我们一定要排除负数,因为c一但是负数,代表着此时非法,代表着这个变化是不对的,经过观察我们很明显的发现,如果有负数,说明肯定是由上一个c的取值集合中,最小的0进行了-1操作,所以我们只允许它+1即可。换句话说,一旦最小值出现了负数,我们重置为1即可。
4、我们真的需要维护整个集合吗?
很容易发现,集合中的数字,要么是1 3 5 7.../0 2 4 6 ...,一定是这样的,也就是说一定是一个等差数列
这时我们就没必要维护整个序列,只需要最小值、最大值和差值就可以知道整个集合中的所有数
这是一个非常重要的转化
因此,我们只维护mx、mn来表示c的取值集合就好。
并且最终答案也要看c取值集合中最小值
5、如果最后取值集合中,不空且有0的存在,说明我们最后肯定能通过有限位置的变换来让字符串合法
其实,我们统计这个c的取值集合,是在模拟所有的变换括号方向的方法合集,最终找到一个符合要求的
步骤 2 :实现
class Solution {
public:
bool canBeValid(string s, string locked) {
int n = s.length();
if (n % 2) {//偶数长度才能左右匹配
return false;
}
int mn = 0, mx = 0;
for (int i = 0; i < n; i++) {
if (locked[i] == '1') {//当前位置不可变换
int d = s[i] == '(' ? 1 : -1;
mx += d;//mx mn同步变化,因为这个位置不可变化
if (mx < 0) {//如果c的取值集合中,最大值都是负值且这个位置不可变化,说明所有可能都对应非法,这个串一定非法
return false;
}
mn += d;
}
else {//遇到可变化位置
mn--;
mx++;
}
if (mn < 0) {
mn = 1;//出现这种情况,说明在0到i-1范围内有种情况左右刚好匹配,且此时locked[i]=0,此时变化为)肯定会非法,所以只能是变为(,mn记为1
}
}
return mn == 0;
}
};
问题 2: 所有位置可换,求最少交换次数
我们以 力扣1963. 使字符串平衡的最小交换次数 为例对该问题进行求解
步骤 1 :分析并排序
1、这个题本质上也是个括号匹配的问题,所以就逃不出通用思路,只是任意位置都可以替换,让我们找到最少的替换次数。
2、因为全部位置可换,所以一定会换成合法的,并且题中要最少的交换次数,所以我们就要让交换效率最大化:
已经是匹配的括号就没必要再交换,我们不管他们
我们只保留非法的左括号和右括号,只要将之替换成合法的,整个串就是合法的
并且这个再替换的时候,对已经合法的部分是没任何影响的。
3、由题意分析,非法的左右括号数量一致,所以统计一个就行。
4、最后,我们发现规律,最有效的将]]][[[这种非法串变成合法的方式,利用数学计算即可算出需要几步。
步骤 2 :实现
class Solution {
public:
int minSwaps(string s) {
/*
1、什么是平衡的?只要任意一个[在其右侧都有一个]与之对应;只要任意一个]在其左侧都有一个[与之对应 -> 就是平衡串
但是现在并不是所有[或者]都能匹配上,所以才有本题,我们要用最少的交换次数让 -> 所有的左右括号对应上
贪心的想,已经匹配上的括号,就不需要管他,因为我们处理没匹配上的括号时,和已经匹配好的没任何关系
所以我们的任务就是,去掉已经匹配好的括号,统计出有多少没匹配上的[ ](显而易见二者数量一致,统计一个就行),用最有效率方法处理非法部分到匹配成功即可
*/
/*
// ][ 1 0 1交换
// ]][[ 1 0 3交换
// ]]][[[ 2 0 5交换 2 3交换
// ]]]][[[[ 2 0 7交换 2 5交换
// ]]]]][[[[[ 3 0 9交换 2 7交换 4 5交换
*/
/*
所以 left为偶数,ans=left/2 ; left为奇数,ans = left/2+1
*/
int n = s.size();
int left = 0;
for (int i = 0; i < n; i++) {
if (s[i] == '[') {
left++;
}
else if (left > 0) {//s[i]==']'且当前还有可用的[与之匹配
left--;//去掉已经匹配好的左右括号,只保留非法部分
}
//s[i]==']'且当前没有可用的[与之匹配,说明这是非法],但是因为非法左右括号数量一致,统计一个就行
}
return left / 2 + left % 2;
}
};