🚩传送门:https://leetcode-cn.com/problems/different-ways-to-add-parentheses/

题目

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

示例 1:

输入: “2-1-1” 输出: [0, 2] 解释: ((2-1)-1) = 0 (2-(1-1)) = 2

示例 2:

输入: “23-45” 输出: [-34, -14, -10, -10, 10] 解释: 2 -2 -5 -17 10 -10 -10 -34 (2(3-(45))) = -34 ((23)-(45)) = -14 ((2(3-4))5) = -10 (2((3-4)5)) = -10 (((23)-4)5) = 10

解题思路:分治

分治算法三步走

  1. 分解:分成部分,分别求解
  2. 解决:实现一个递归函数,输入算式,返回算式解
  3. 合并:合并部分的解,得出最终解

本题解 采用了 分治 的思想:

  1. [分解]:遍历字符串,遇到操作符,就将左右两边的字符串,分别当作 两个表达式
  2. [解决]:带入算式,求解两侧的答案集合
  3. [合并]:将两侧的答案集合通过当前操作符组合成心的答案集合。

简洁的说明代码:

  1. class Solution:
  2. def diffWaysToCompute(self, input: str) -> List[int]:
  3. # 如果只有数字,直接返回
  4. if input.isdigit():
  5. return [int(input)]
  6. res = []
  7. # 依次遍历字符串,读取运算符
  8. for i, char in enumerate(input):
  9. if char in ['+', '-', '*']:
  10. # 1.分解:遇到运算符,计算左右两侧的结果集
  11. # 目的:解决diffWaysToCompute 递归函数求出子问题的解
  12. left = self.diffWaysToCompute(input[:i]) #left得到的是左侧结果集
  13. right = self.diffWaysToCompute(input[i+1:]) #right得到的是右侧结果集
  14. # 2.合并:根据运算符合并子问题的解
  15. for l in left:
  16. for r in right:
  17. if char == '+':
  18. res.append(l + r)
  19. elif char == '-':
  20. res.append(l - r)
  21. else:
  22. res.append(l * r)
  23. return res

解题思路:动态规划

dp 数组的含义比较难定义,需要做一个预处理,把字符串中的每个数字和运算符分开存起来 。

这样的话我们就有了两个 list,一个保存了所有数字,一个保存了所有运算符。

举例子:2 3 - 4 5

存起来的数字是 numList = [2345] 存起来的运算符是 opList = [, -, ]

👉 定义 含义是第 到第 个数字(从 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]。

  1. package dp.dp241;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class demo {
  5. public static List<Integer> diffWaysToCompute(String input) {
  6. List<Integer> numList = new ArrayList<>();
  7. List<Character> opList = new ArrayList<>();
  8. char[] array = input.toCharArray();
  9. int num = 0;
  10. //=预处理部分=
  11. for (int i = 0; i < array.length; i++) {
  12. if (isOperation(array[i])) {
  13. //由于之前处理的整数并未存储,此处存储
  14. numList.add(num);
  15. num = 0;
  16. opList.add(array[i]);
  17. continue;
  18. }
  19. //整数处理
  20. num = array[i] - '0';
  21. }
  22. numList.add(num);
  23. int N = numList.size(); // 数字的个数
  24. // 初始条件:一个数字
  25. ArrayList<Integer>[][] dp = (ArrayList<Integer>[][]) new ArrayList[N][N];
  26. for (int i = 0; i < N; i++) {
  27. ArrayList<Integer> result = new ArrayList<>();
  28. result.add(numList.get(i));
  29. dp[i][i] = result;
  30. }
  31. // 2 个数字到 N 个数字
  32. for (int n = 2; n <= N; n++) { //n代表每次处理字段的长度
  33. for (int i = 0; i < N; i++) { // 开始下标
  34. int j = i + n - 1; // 结束下标
  35. if (j >= N) { //下标越界
  36. break;
  37. }
  38. ArrayList<Integer> result = new ArrayList<>();
  39. // 分成 i ~ s 和 s+1 ~ j 两部分,处理的字段,遍历字段起始到终止
  40. for (int s = i; s < j; s++) {
  41. ArrayList<Integer> result1 = dp[i][s];
  42. ArrayList<Integer> result2 = dp[s + 1][j];
  43. //分成两段的拼接
  44. for (int x = 0; x < result1.size(); x++) {
  45. for (int y = 0; y < result2.size(); y++) {
  46. // 第 s 个数字下标对应是第 s 个运算符
  47. char op = opList.get(s);
  48. result.add(caculate(result1.get(x), op, result2.get(y)));
  49. }
  50. }
  51. }
  52. dp[i][j] = result;
  53. }
  54. }
  55. return dp[0][N-1];
  56. }
  57. //计算
  58. private static int caculate(int num1, char c, int num2) {
  59. switch (c) {
  60. case '+':
  61. return num1 + num2;
  62. case '-':
  63. return num1 - num2;
  64. case '*':
  65. return num1 * num2;
  66. }
  67. return -1;
  68. }
  69. private static boolean isOperation(char c) {
  70. return c == '+' || c == '-' || c == '*';
  71. }
  72. }

解释:

ArrayList<Integer>[][] dp 是三维数组,可以利用ArrayList本身的特性来形式上降维。