Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务活动详情
一、题目描述
给定一个二进制表达式,包含()、1、0、&、| 几种符号,每次可以反转修改1、0、&、|,变成0、1、|、&。求最少需要修改几次,才能改变整个表达式的值。
数据范围
字符串长度 l <= 10^5
二、思路分析
想了二十分钟,先考虑怎么单纯的求值,用一个栈就可以了,遇到符号就入栈,遇到数字就合并,遇到左括号就入栈,遇到右括号就和左括号合并(此时括号内只会有一个数字)。但是接下来就卡住了,要怎么做才能最小修改呢?
尝试去列举了八种最简单的情况,基本都是修改一次两次即可。
我们是否可以考虑DP呢?但是每个数字修改后,对后面其实是会有后效性的,怎么解决这个问题?
通过思考我们发现,同一层之间是不会有后效性的,因为没有括号带来的“延迟计算”(有括号时,我们会出现当前的结果不能计算,得存起来)。同层之间没有后效性的问题也就使得我们在同一层内可以用 DP 来计算,不同层之间用栈来存储,等到它们直接同一层了(遇到右括号时),再合并起来。
借鉴一下自己写剑指Offer 224 时的思路,两个栈,一个存当前层的状态,一个存符号。每一层只需要用一个坑位来表示它的状态,同层之间可以立即计算,不用考虑后效性的问题。
新的问题是,状态如何表示呢?当计算未结束的时候,我们只知道我们需要翻转,但并不知道要转到 1 还是 0 ,故需要将两种状态同时记录下来。用一对数字表示,一个表示变成 0 所需要的最小操作数,另一个表示变成 1 所需要的最小操作数,两个状态之间的合并是很显然的。
三、AC代码
class Solution {
public:
struct node{
int x,y;
};
stack<node> num;
stack<char> op;
node merge(node aa, node bb, int flag){
// flag==1 is "&" , flag==2 is "|"
node cc;
if (flag == 1) {
cc.x = min(min(aa.x, bb.x), 1 + aa.x + bb.x);
cc.y = min(aa.y + bb.y, 1 + min(aa.y, bb.y));
} else {
cc.x = min(aa.x + bb.x, 1 + min(aa.x, bb.x));
cc.y = min(min(aa.y, bb.y), 1 + aa.y + bb.y);
}
return cc;
}
void cacl(){
if (op.empty()) return;
char ch = op.top();
if (ch == '(') return;
op.pop();
node b = num.top();num.pop();
node a = num.top();num.pop();
node c = merge(a, b, ch == '&' ? 1 : 2);
num.push(c);
return;
}
int minOperationsToFlip(string expression) {
string s = expression;
int l = s.length();
for (int i=0; i<l; i++) {
if (s[i] == '(') {
op.push(s[i]);
} else
if (s[i] == ')') {
op.pop();
cacl();
} else
if (s[i] == '|' || s[i] == '&') {
op.push(s[i]);
} else {
int now = s[i] - '0';
node cc;
cc.x = now;
cc.y = now^1;
num.push(cc);
cacl();
}
}
node ans = num.top();
if (ans.x == 0) return ans.y;
else return ans.x;
}
};
四、总结
在写代码时遇到的一些坑: 表达式问题一定要非常注意一些边界问题(栈问题也是),最重要的是每次合并时要判断能否合并,如果迎接你的也是一个左括号(这一层没有数字),这就不能合并,要判断;每层的第一个数字也不能执行合并操作。
对于本题,最重要的一个点是,认识到每一层的层内是没有后效性的,这决定了我们可以使用 DP