LeetCode #241 Different Ways to Add Parentheses

题目

Given a string of numbers and operators, return all possible results from computing all the different possible ways to group numbers and operators. The valid operators are +, - and *.

Example 1

Input: "2-1-1".

((2-1)-1) = 0
(2-(1-1)) = 2

Output: [0, 2]

Example 2

Input: "2*3-4*5"

(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10

Output: [-34, -14, -10, -10, 10]

解题思路

加括号的问题,事实上就是哪个运算先做,哪个运算后做的问题。因此我们可以用运算符作为根结点,运算数作为子结点把一个表达式展开成一棵树的形式,属于同一个子树的两个运算数先做运算。最后看看展开的共有多少棵不同的树,相对应的就有几个结果。比如上面的第二个例子,画成树的形式如下:

     *                   -                   *              *                      *
   /   \               /   \               /   \          /   \                  /   \
  2     -             *     *             *     5        2     *                -     5
      /   \         /  \   /  \         /   \                /   \            /   \
     3     *       2    3 4    5       2     -              -     5          *     4
         /   \                             /   \          /   \            /   \ 
        4     5                           3     4        3     4          2     3

      运算树1           运算树2             运算树3            运算树4            运算树5

树的结构本来就是递归定义的,因此这道题的解题方法就是分治和递归。对于输入的字符串,我们可以分别用扫描到的每个运算符作为父结点,然后把整个字符串划分为两部分(即两个子结点)分别进行运算再合并,相当于把原问题划分成两个子问题(此时原问题和子问题都是同类型的问题!即都是加括号求表达式的问题,只不过子问题的规模缩小了),如此一直划分下去,直到划分到的子串中再也不包含运算符,此时把该子串转化为整数返回即可。

注意每一层递归应该返回对应子树计算的所有可能结果,比如上图中的 运算树1运算树4 ,他们对应的表达式都是 (2 * (第二层子树)),但是这个第二层子树根据不同的结构可以得到两种结果 3 - (4 * 5) = -17(3 - 4) * 5 = -5 ,因此在递归调用时,应该返回包含这两个结果的一个 vector ,然后分别用 2 和这两种结果 -17-5 相乘得到最终的两种结果,只有这样才不会漏解。

这样的分治递归显然是 make sense 的,但是可以再优化。观察上面的运算树我们可以发现:运算树1运算树2 的叶子结点都要计算 4 * 5运算树3运算树4 的叶子结点都要计算 3 - 4运算树2运算树5 的叶子结点都要计算 2 * 3 ,这些叶子结点在递归的过程中都会碰到,从而进行了不必要的重复计算。我们可以利用动态规划的思想去建造一张表达式对应其运算结果的表,每次递归时先检查表中是否已经保存有相同表达式的运算结果,若有则直接利用这个运算结果,不需要再向下进行递归。

Java代码实现

class Solution {
    public List<Integer> diffWaysToCompute(String input) {
        Map<String, List<Integer> > expressions = new HashMap<>();
        return getResult(input, expressions);
    }

    public List<Integer> getResult(String input, Map<String, List<Integer> > expressions) {
        List<Integer> immediateRst = new ArrayList<>();
        for (int i = 0; i < input.length(); ++i) {
            if (input.charAt(i) == '+' || input.charAt(i) == '-' || input.charAt(i) == '*') {
                String left = input.substring(0, i);
                String right = input.substring(i + 1);
                List<Integer> l = null;
                List<Integer> r = null;

                // 先从哈希表中寻找表达式对应的结果值,如果找不到,再进行递归
                if (expressions.containsKey(left)) {
                    l = expressions.get(left);
                } else {
                    l = getResult(left, expressions);
                }
                if (expressions.containsKey(right)) {
                    r = expressions.get(right);
                } else {
                    r = getResult(right, expressions);
                }

                // 把得到的两组结果两两分别运算
                for (Integer num1 : l) {
                    for (Integer num2 : r) {
                        switch(input.charAt(i)) {
                            case '+':
                                immediateRst.add(num1 + num2);
                                break;
                            case '-':
                                immediateRst.add(num1 - num2);
                                break;
                            case '*':
                                immediateRst.add(num1 * num2);
                        }
                    }
                }
            }
        }

        // 如果是叶子结点,则直接转化为整数存入列表中,且叶子结点不需要保存进哈希表
        if (immediateRst.isEmpty()) { immediateRst.add(Integer.parseInt(input)); }
        else { expressions.put(input, immediateRst); }

        return immediateRst;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值