LeetCode 分治法

@

241. 为运算表达式设计优先级

给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。

  • 分治思想:

    • 将加括号转化为,对每个运算符号,先执行处理两侧的数学表达式,再处理此运算符号。
    • 利用递归将当前遍历符号的左右两侧表达式继续拆分。
    • 边界情况下,字符串无运算符号,只有数字。
  • 例:5 + 3 * 4 + 2

    • left -> 5
    • right -> 3 * 4 + 2
      • left -> 3
      • right -> 4 + 2
        • left -> 4
        • right -> 2
      • left -> 3 * 4
        • left -> 3
        • right -> 4
      • right -> 2
    • left : 5, right: 3 * (4 + 2) = 18, (3 * 4) + 2 = 14
    • (3 * 4 + 2); (3 * 4) + 2; 3 * (4 + 2); 前两种结果相同
class Solution {
public:
    vector<int> diffWaysToCompute(string expression) {
        int n = expression.length();
        vector<int> ways;
        for (int i = 0; i < n; i++) {
            char c = expression[i];
            if (c == '+' || c == '-' || c == '*') {
            	// 符号左右两侧继续划分
                vector<int> left = diffWaysToCompute(expression.substr(0, i));
                vector<int> right = diffWaysToCompute(expression.substr(i + 1));
                for (const int& l : left) {
                    for (const int& r : right) {
                        switch(c) {
                            case '+': ways.push_back(l + r); break;
                            case '-': ways.push_back(l - r); break;
                            case '*': ways.push_back(l * r); break;
                        }
                    }
                }
            }
        }
        if (ways.empty()) ways.push_back(stoi(expression)); // 边界情况处理
        return ways;
    }
};
  • 记忆化存储
    • 某些被划分的字符串可能重复出现多次,可以使用hash map将这些结果存储起来,再次遍历到该字符串时直接查询存储的结果。
class Solution {
public:
    unordered_map<string, vector<int>> strMap; // 外部建立哈希映射表
    vector<int> diffWaysToCompute(string expression) {
        int n = expression.length();
        // 查看哈希表中是否存储了该表达式划分的结果
        if (strMap.count(expression)) {
            return strMap[expression];
        }
        vector<int> ways;
        for (int i = 0; i < n; i++) {
            char c = expression[i];
            if (c == '+' || c == '-' || c == '*') {
                vector<int> left = diffWaysToCompute(expression.substr(0, i));
                // 将左侧表达式及其对应的结果放入哈希映射表中
                pair<string, vector<int>> instance1(expression.substr(0, i), left);
                strMap.insert(instance1);
                vector<int> right = diffWaysToCompute(expression.substr(i + 1));
                pair<string, vector<int>> instance2(expression.substr(i + 1), right);
                strMap.insert(instance2);
                for (const int& l : left) {
                    for (const int& r : right) {
                        switch(c) {
                            case '+': ways.push_back(l + r); break;
                            case '-': ways.push_back(l - r); break;
                            case '*': ways.push_back(l * r); break;
                        }
                    }
                }
            }
        }
        if (ways.empty()) ways.push_back(stoi(expression));
        return ways;
    }
};
  • 动态规划:
    • 从下而上处理
    • dp[i][j][] 表示第 i 个数至第 j 个数的所有可能运算结果
    • j = i -> 0 方向计算 dp[j][i];设 i = 3;j = 3,2,1,0;则先计算得到dp[3][3],dp[2][3],dp[1][3],dp[0][3];dp[2][3] = dp[2][2] * dp[3][3];dp[1][3] = dp[1][1] * dp[2][3]; dp[1][2] * dp[3][3];
    • j = 0 -> i 方向计算 dp[j][i];设 i = 3;j = 0,1,2,3;则先计算dp[0][3],但是dp[0][3] = dp[0][0] * dp[1][3];dp[0][1] * dp[2][3]; dp[0][2] * dp[3][3];的结果,若按此顺序,dp[2][3]的结果还未计算,因此无法得到正确结果;
class Solution {
public:
    vector<int> diffWaysToCompute(string expression) {
        istringstream ss(expression + "+"); // 增加一个标志便于截止
        vector<int> data;
        vector<char> ops;
        int num = 0;
        char op = ' ';
        // 获取表达式中的数字和运算符号
        while (ss >> num && ss >> op) {
            data.push_back(num);
            ops.push_back(op);
        }
        int n = data.size();
        vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>()));
        for (int i = 0; i < n; i++) {
            for (int j = i; j >= 0; j--) {
                if (i == j) {
                    dp[j][i].push_back(data[i]); // 第 i 个数字本身
                }else {
                    for (int k = j; k < i; k += 1) { // 自 j 向 i 步进,考虑所有情况
                    	// 计算得到第 j 个数到第 i 个数的所有运算结果,其用 k 进行分割
                    	// j 至 i 可以分为 j 至 k 与 k + 1 到 i
                        for (const int& left : dp[j][k]) {
                            for (const int& right : dp[k + 1][i]) {
                                int val = 0;
                                // 以 k 为分界,所以调用第 k 个运算符
                                switch(ops[k]) {
                                    case '+' : val = left + right; break;
                                    case '-' : val = left - right; break;
                                    case '*' : val = left * right; break;
                                }
                                dp[j][i].push_back(val); // 存储结果
                            }
                        }
                    }
                }
            }
        }
        return dp[0][n - 1];
    }
};
posted @ 2023-02-15 21:16  GreyWang  阅读(19)  评论(0编辑  收藏  举报