给定一个布尔表达式和一个期望的布尔结果 result,布尔表达式由 0
(false)、1
(true)、&
(AND)、 |
(OR) 和 ^
(XOR) 符号组成。实现一个函数,算出有几种可使该表达式得出 result 值的括号方法。
示例 1:
输入: s = “1^0|0|1”, result = 0
输出: 2
解释: 两种可能的括号方法是
1^(0|(0|1))
1^((0|0)|1)
示例 2:
输入: s = “0&0&0&1^1|0”, result = 1
输出: 10
提示:
点击此处跳转题目。
这道题的主要问题在于思路,即如何将括号方法进行计算机表示。括号表示优先结合,具体如下:
∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗ \begin{array}{c} \ *\ |\ *\ *\ *\ *\\ \ *\ *\ |\ *\ *\ *\\ \ *\ *\ *\ |\ *\ *\\ \ *\ *\ *\ *\ |\ * \end{array} ∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗ ∗ ∗ ∗ ∗ ∗ ∣ ∗
求解左右两部分的值,依据运算符 ‘|’ 计算并返回结果,最终得到答案。
使用 dp 二维数组存储动态规划结果,每个元素 dp[i, j](记为 elem)为长度为 2 的一维数组,elem[0] 表示表达式 i ~ j 计算得到 0 的方法数,elem[1] 表示计算得到 1 的方法数。具体代码如下:
public class Solution {
public int CountEval(string s, int result) {
int n = s.Length / 2 + 1; // s 中数字的个数
int[] nums = new int[n]; // 存储 s 中的数字
int[,][] dp = new int[n, n][]; // dp 数组,dp[i, j][k] 存储长度为 i~j 的表达式计算出结果 k 的方法数
for (int i = 0; i < n; i++) {
if (s[i * 2] == '1') nums[i] = 1; // 初始化 nums
for (int j = 0; j < n; j++) dp[i, j] = new int[2]; // 初始化 dp
}
Partition(dp, nums, s, 0, nums.Length - 1);
return dp[0, nums.Length - 1][result];
}
// 递归求解
public int[] Partition(int[,][] dp, int[] nums, string s, int i, int j) {
if (i == j) { // 递归出口
dp[i, j][nums[i]]++;
return dp[i, j];
}
int[] ans = dp[i, j];
for (int k = i; k < j; k++) {
// 取出左右两边的计算结果和运算符,如果结果为 0,则重新计算
int[] left = dp[i, k][0] + dp[i, k][1] == 0 ? Partition(dp, nums, s, i, k) : dp[i, k];
int[] right = dp[k + 1, j][0] + dp[k + 1, j][1] == 0 ? Partition(dp, nums, s, k + 1, j) : dp[k + 1, j];
char op = s[k * 2 + 1];
// 合并结果
ans[Calc(0, 0, op)] += left[0] * right[0];
ans[Calc(0, 1, op)] += left[0] * right[1];
ans[Calc(1, 0, op)] += left[1] * right[0];
ans[Calc(1, 1, op)] += left[1] * right[1];
}
return ans;
}
// 计算 a op b 的值
public int Calc(int a, int b, char op) {
return op switch {
'^' => a ^ b,
'&' => a & b,
'|' => a | b
};
}
}