列表

详情


面试题 08.14. 布尔运算

给定一个布尔表达式和一个期望的布尔结果 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

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
class Solution { public: int countEval(string s, int result) { } };

golang 解法, 执行用时: 0 ms, 内存消耗: 1.8 MB, 提交时间: 2022-11-30 21:31:00

func countEval(s string, result int) int {
	var dp [40][40][2]int
	l := len(s)
	for i := 0; i < l; i +=2{
		dp[i][i][int(s[i]-'0')] = 1
	}
	// printDP(&dp)
	// 区间宽度
	for i := 2; i < l + 1; i += 1{
		//区间起点
		for j := 0; j < l-i + 1; j += 1{
			// 区间运算符号的位置
			for k := j+1; k < i + j; k +=1 {
				getAns(s, j, k, i+j, &dp)
			}
		}
	}
	return dp[0][len(s)-1][result]

}


func getAns(s string, i, mid, j int, dp *[40][40][2]int){
	switch s[mid]{
	case '&':
		dp[i][j][0] += dp[i][mid-1][0] * dp[mid+1][j][0]
		dp[i][j][0] += dp[i][mid-1][1] * dp[mid+1][j][0]
		dp[i][j][0] += dp[i][mid-1][0] * dp[mid+1][j][1]
		dp[i][j][1] += dp[i][mid-1][1] * dp[mid+1][j][1]
	case '^':
		// 0
		dp[i][j][0] += dp[i][mid-1][0] * dp[mid+1][j][0]
		dp[i][j][0] += dp[i][mid-1][1] * dp[mid+1][j][1]
		// 1
		dp[i][j][1] += dp[i][mid-1][1] * dp[mid+1][j][0]
		dp[i][j][1] += dp[i][mid-1][0] * dp[mid+1][j][1]
	case '|':
		dp[i][j][1] += dp[i][mid-1][1] * dp[mid+1][j][0]
		dp[i][j][1] += dp[i][mid-1][0] * dp[mid+1][j][1]
		dp[i][j][1] += dp[i][mid-1][1] * dp[mid+1][j][1]
		dp[i][j][0] += dp[i][mid-1][0] * dp[mid+1][j][0]
	}
}

golang 解法, 执行用时: 0 ms, 内存消耗: 2.5 MB, 提交时间: 2022-11-30 21:30:45

//dp[i][j][0/1],表示s[i:j+1]结果为0/1的括号方法种数
func countEval(s string, result int) int {
	dp := make([][][2]int, len(s))
	for i := 0; i < len(dp); i++ {
		dp[i] = make([][2]int, len(s))
	}
	//dp[0][len-1][result]即为答案,i需要倒序遍历,j正序遍历
	for i := len(s) - 1; i >= 0; i -= 2 {
		for j := i; j < len(s); j += 2 {
			if i == j {
				if s[i] == '0' {
					dp[i][j][0]++
				} else {
					dp[i][j][1]++
				}
				continue
			}
			/*
				操作符s[k]将s[i:j+1]分为s[i:k]和s[k+1:j+1]分为前后两块
				遍历各自dp[i][k-1][0/1]和dp[k+1][j][0/1]共四种情况,累计dp[i][j][0/1]的值
			*/
			for k := i + 1; k < j; k += 2 {
				for arg1 := 0; arg1 <= 1; arg1++ {
					for arg2 := 0; arg2 <= 1; arg2++ {
						if doBoolOp(arg1, arg2, s[k]) == 0 {
							dp[i][j][0] += dp[i][k-1][arg1] * dp[k+1][j][arg2]
						} else {
							dp[i][j][1] += dp[i][k-1][arg1] * dp[k+1][j][arg2]
						}
					}
				}

			}
		}
	}
	return dp[0][len(s)-1][result]
}

func doBoolOp(arg1, arg2 int, operator byte) int {
	if operator == '&' {
		return arg1 & arg2
	} else if operator == '|' {
		return arg1 | arg2
	} else {
		return arg1 ^ arg2
	}
}

cpp 解法, 执行用时: 12 ms, 内存消耗: 7.8 MB, 提交时间: 2022-11-30 21:29:28

class Solution {
public:

	unordered_map<string, pair<int, int > > dp;
	int countEval(string s, int result) {
		pair<int, int > ans = helper(s);
		//pair<int, int > 中第一个数表示结果中0的出现次数,第二个数表示1的出现次数
		if (result == 0)
			return ans.first;
		else
			return ans.second;
	}

	pair<int, int > helper(string s) {
		if (dp.count(s) != 0)
			return dp[s];
		int ans[2] = { 0 };
		int len = s.size();
		if (len == 1)
			if (s == "1")
				ans[1]++;
			else
				ans[0]++;
		else {
			for (int i = 1; i < len; i += 2) {

				//不断的找分割点,类似求卡特兰数的思想
				//递归的获得 分割点前后字符串可以生成多少个括号位置不同的0或者1
				pair<int, int > t1 = helper(s.substr(0, i));
				pair<int, int > t2 = helper(s.substr(i+1));
				switch (s[i]) {
				case '&':  //分割点为 & ,获得0 有 0&0 ,1&0 , 0&1 三种情况,交叉相乘后累加这三种情况 
					ans[0] += t1.first * t2.first + t1.second *t2.first + t2.second*t1.first;
					ans[1] += t1.second * t2.second; // 要想获得 1 只有 1&1 这一种情况
					break;

				case '|':// 同理
					ans[0] += t1.first * t2.first;
					ans[1] += t1.second * t2.second + t1.first * t2.second + t2.first * t1.second;;
					break;
				case '^':// 同理
					ans[0] += t1.first * t2.first + t1.second * t2.second;
					ans[1] += t1.first * t2.second + t2.first * t1.second;
					break;
				}
			}
		}
		dp[s] = make_pair(ans[0], ans[1]);
		return { ans[0] ,ans[1] };
	}
};

python3 解法, 执行用时: 84 ms, 内存消耗: 15.7 MB, 提交时间: 2022-11-30 21:27:48

class Solution:
    def countEval(self, s: str, result: int) -> int:
        operations = {
            "&":{
                True:[(True,True)],
                False:[(True,False),(False,False),(False,True)]
            },
            "|":{
                True:[(True,True),(True,False),(False,True)],
                False:[(False,False)]
            },
            "^":{
                True:[(True,False),(False,True)],
                False:[(False,False),(True,True)]
            }
        }
        @lru_cache(maxsize=1000000000)
        def dfs(s, target):
            if len(s)==1:
                lp = int(s)
                return int(lp == target)
            cnt = 0
            for i,c in enumerate(s):
                if c in operations:
                    for lta,rta in operations[c][target]:
                        cnt+=dfs(s[:i], lta) * dfs(s[i+1:], rta)
            return cnt
        return dfs(s, int(result))

javascript 解法, 执行用时: 68 ms, 内存消耗: 45.2 MB, 提交时间: 2022-11-30 21:26:48

/**
 * @param {string} s
 * @param {number} result
 * @return {number}
 */
/**
 * @param {string} s
 * @param {number} result
 * @return {number}
 */
var countEval = function (s, result) {

    let row = s.length;  

    // 创建一个三维数组,第三维长度为2(第三维可以这样理解,一个区间的result 状态情况,要么为0,要么为1),并初始化为0.  
    // 比如 row=2  => [ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 0, 0 ] ] ]
    // dp的含义  dp[i][j][result]   : i,j 区间的字符串得到状态 result 的方案数目

    let dp = Array.from(Array(row), () => { return Array.from(Array(row), () => Array(2).fill(0)) })

    for (let i = 0; i < row; i++) {      //初始化
        if (s[i] == '0') {         
            dp[i][i][0] = 1          //只有一个字符,得到0的方案数为1 (得到1的方案数为0,前面构建数组的时候,已经填充过了)
        } else if (s[i] == '1') {
            dp[i][i][1] = 1          //只有一个字符,得到1的方案数为1
        }
    }

    for (let len = 2; len < s.length; len++) {         //长度为2 ,开始从左往右扫描(len的长度不断增加,是因为:大区间的结果依赖于小区间的结果,必须从小区间开始计算)
        for (let i = 0; i + len < s.length; i += 2) {
            let j = i + len;                          // i,j  区间内判断,j是终点
            for (let k = i + 1; k < j; k += 2) {      // k 为 i,j 区间的 符号 ,以k为分割点,左边字符区间:  [i][k-1]  右边[k+1][j]
                if (s[k] == '&') {  
//在 分割点是 & 的情况下, 计算状态 。 比如 dp[i][j][0]  只有左右字符状态为: 0 0 和 0 1 和  1 0 这三种情况。 求方法数,用乘法(比如 左边字符串 有2 种方案得状态0,右边有2种方案得到状态1. 那么总字符串得到0的方案是:2*2=4
                    dp[i][j][0] += dp[i][k - 1][0] * dp[k + 1][j][0] + dp[i][k - 1][0] * dp[k + 1][j][1] + dp[i][k - 1][1] * dp[k + 1][j][0]
                    dp[i][j][1] += dp[i][k - 1][1] * dp[k + 1][j][1]
                } else if (s[k] == '|') {         
                    dp[i][j][0] += dp[i][k - 1][0] * dp[k + 1][j][0]
                    dp[i][j][1] += dp[i][k - 1][1] * dp[k + 1][j][1] + dp[i][k - 1][1] * dp[k + 1][j][0] + dp[i][k - 1][0] * dp[k + 1][j][1]
                } else if (s[k] == '^') {
                    dp[i][j][0] += dp[i][k - 1][0] * dp[k + 1][j][0] + dp[i][k - 1][1] * dp[k + 1][j][1]
                    dp[i][j][1] += dp[i][k - 1][1] * dp[k + 1][j][0] + dp[i][k - 1][0] * dp[k + 1][j][1]
                }
            }
        }
    }

    return dp[0][row - 1][result]        // 求整个区间的方案数
};

上一题