化繁为简的分治法:——(LC241(我太菜了做得头疼))

题目:

给定一个只包含加、减和乘法的数学表达式,求通过加括号可以得到多少种不同的结果

输入是一个字符串,表示数学表达式;输出是一个数组,存储所有不同的加括号结果。

题解:

顾名思义,分治问题由“分”(divide)和“治”(conquer)两部分组成,通过把原问题分为子
问题,再将子问题进行处理合并,从而实现对原问题的求解。

例如:在排序章节展示的归并排序就是典型的分治问题,其中“分”即为把大数组平均分成两个小数组,通过递归实现,最终我们会得到多个长度为 1 的子数组;“治”即为把已经排好序的两个小数组合成为一个排好序的大数组,从长度为 1 的子数组开始,最终合成一个大数组。

自上而下的分治可以和 memoization 结合,避免重复遍历相同的子问题。如果方便推
导,也可以换用自下而上的动态规划方法求解。

利用分治思想,我们可以把加括号转化为,对于每个运算符号,先执行处理两侧的数学表达
式,再处理此运算符号

最巧妙的地方就是做一个预处理,把每个数字提前转为 int 然后存起来,同时把运算符也都存起来。这样的话我们就有了两个 list,一个保存了所有数字,一个保存了所有运算符。

2 * 3 - 4 * 5

存起来的数字是 numList = [2 3 4 5],

存起来的运算符是 opList = [*, -, *]。

dp[i][j] 也比较好定义了,含义是第 i 到第 j 个数字(从 0 开始计数)范围内的表达式的所有解。

举个例子,2 * 3 - 4 * 5 dp[1][3] 就代表第一个数字 3 到第三个数字 5 范围内的表达式 3 - 4 * 5 的所有解。

初始条件的话,也很简单了,就是范围内只有一个数字。

2 * 3 - 4 * 5

dp[0][0] = [2],dp[1][1] = [3],dp[2][2] = [4],dp[3][3] = [5]。

 

有了一个数字的所有解,然后两个数字的所有解就可以求出来。

有了两个数字的所有解,然后三个数字的所有解就和解法一求法一样。

把三个数字分成两部分,将两部分的解两两组合起来即可。

两部分之间的运算符的话,因为表达式是一个数字一个运算符,所以运算符的下标就是左部分最后一个数字的下标。
看下边的例子。

2 * 3 - 4 * 5
存起来的数字是 numList = [2 3 4 5],
存起来的运算符是 opList = [*, -, *]。

假设我们求 dp[1][3]
也就是计算 3 - 4 * 5 的解
分成 3 和 4 * 5 两部分,3 对应的下标是 1 ,对应的运算符就是 opList[1] = '-' 。
也就是计算 3 - 20 = -17
    
分成 3 - 4 和 5 两部分,4 的下标是 2 ,对应的运算符就是 opList[2] = '*'。
也就是计算 -1 * 5 = -5
    
所以 dp[1][3] = [-17 -5]

四个、五个... 都可以分成两部分,然后通过之前的解求出来。

直到包含了所有数字的解求出来,假设数字总个数是 ndp[0][n-1] 就是最后返回的了。

class Solution {
 public List<Integer> diffWaysToCompute(String input) {
    List<Integer> numList = new ArrayList<>();
    List<Character> opList = new ArrayList<>();
    char[] array = input.toCharArray();
    int num = 0;
    for (int i = 0; i < array.length; i++) {
        if (isOperation(array[i])) {
            numList.add(num);
            num = 0;
            opList.add(array[i]);
            continue;
        }
        num = num * 10 + array[i] - '0';
    }
    numList.add(num);
    int N = numList.size(); // 数字的个数

    // 一个数字
    ArrayList<Integer>[][] dp = (ArrayList<Integer>[][]) new ArrayList[N][N];
    for (int i = 0; i < N; i++) {
        ArrayList<Integer> result = new ArrayList<>();
        result.add(numList.get(i));
        dp[i][i] = result;
    }
    // 2 个数字到 N 个数字
    for (int n = 2; n <= N; n++) {
        // 开始下标
        for (int i = 0; i < N; i++) {
            // 结束下标
            int j = i + n - 1;
            if (j >= N) {
                break;
            }
            ArrayList<Integer> result = new ArrayList<>();
            // 分成 i ~ s 和 s+1 ~ j 两部分
            for (int s = i; s < j; s++) {
                ArrayList<Integer> result1 = dp[i][s];
                ArrayList<Integer> result2 = dp[s + 1][j];
                for (int x = 0; x < result1.size(); x++) {
                    for (int y = 0; y < result2.size(); y++) {
                        // 第 s 个数字下标对应是第 s 个运算符
                        char op = opList.get(s);
                        result.add(caculate(result1.get(x), op, result2.get(y)));
                    }
                }
            }
            dp[i][j] = result;

        }
    }
    return dp[0][N-1];
}

private int caculate(int num1, char c, int num2) {
    switch (c) {
        case '+':
            return num1 + num2;
        case '-':
            return num1 - num2;
        case '*':
            return num1 * num2;
    }
    return -1;
}

private boolean isOperation(char c) {
    return c == '+' || c == '-' || c == '*';
}


}


总结记录来源:力扣(LeetCode)(一位大神的题解)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只程序小洋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值