leetcode
leetcode 201 ~ 250
为运算表达式设计优先级

为运算表达式设计优先级

难度:

标签:

题目描述

给你一个由数字和运算符组成的字符串 expression ,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以 按任意顺序 返回答案。

生成的测试用例满足其对应输出值符合 32 位整数范围,不同结果的数量不超过 104

 

示例 1:

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

示例 2:

输入:expression = "2*3-4*5"
输出:[-34,-14,-10,-10,10]
解释:
(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

 

提示:

  • 1 <= expression.length <= 20
  • expression 由数字和算符 '+''-''*' 组成。
  • 输入表达式中的所有整数值在范围 [0, 99] 

代码结果

运行时间: 23 ms, 内存: 16.1 MB


/*
 题目思路:
 1. 使用Java Streams来简化计算过程。
 2. 同样的递归思路,不同的是通过Stream来简化List的操作。
 3. 对于每个运算符,将表达式分为两部分,使用Stream来处理子表达式的结果组合。
 4. 使用Stream的flatMap和map来处理结果的合并和计算。
 */
import java.util.*;
import java.util.stream.*;
 
public class Solution {
    public List<Integer> diffWaysToCompute(String expression) {
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < expression.length(); i++) {
            char c = expression.charAt(i);
            if (c == '+' || c == '-' || c == '*') {
                List<Integer> left = diffWaysToCompute(expression.substring(0, i));
                List<Integer> right = diffWaysToCompute(expression.substring(i + 1));
                result.addAll(
                    left.stream().flatMap(l -> right.stream().map(r ->
                        c == '+' ? l + r : c == '-' ? l - r : l * r
                    )).collect(Collectors.toList())
                );
            }
        }
        return result.isEmpty() ? Arrays.asList(Integer.parseInt(expression)) : result;
    }
}
 

解释

方法:

这个题解使用分治算法和记忆化搜索来解决问题。对于给定的表达式,如果它不包含任何运算符,直接将其转换为整数并返回。否则,遍历表达式的每个字符,如果遇到运算符,就将表达式分成左右两部分,递归计算左右两部分的所有可能结果,然后根据运算符对左右两部分的结果进行交叉运算,将所有可能的结果加入答案数组。为了避免重复计算,使用一个字典 memory 来存储已经计算过的子表达式及其结果,这样可以大大减少递归次数,提高效率。

时间复杂度:

O(n * 4^n)

空间复杂度:

O(n * 2^n)

代码细节讲解

🦆
在处理表达式时,如何确保遇到多位数时能正确处理?例如表达式 '10+20' 中的数字处理。
在题解中,为了正确处理多位数,算法在遇到运算符时将表达式分成左右两部分。当遍历到表达式中的运算符时,会基于运算符的位置将表达式分割为两部分,然后递归地对这两部分进行处理。如果表达式片段不包含任何运算符,表明这是一个完整的数字,无论它是单位数还是多位数,直接将这个字符串转换为整数。这种方法通过递归确保了即使在运算符之间的数是多位数时也能被正确解析和计算。
🦆
为什么在记忆化搜索中使用字符串作为字典的键,而不是其他数据结构或编码形式?
在记忆化搜索中使用字符串作为字典的键,主要是因为字符串在这种场景下提供了一个直观且易于实现的方式来唯一标识每一个子表达式。字符串能够精确地保持表达式的结构和内容,使得每次递归调用都能快速检查是否已经计算过相同的表达式。使用其他数据结构如数组或编码形式可能会涉及到额外的转换过程,这不仅增加了实现的复杂度,还可能影响查找效率。字符串的哈希查找效率高,易于在字典中进行存储和检索。
🦆
记忆化搜索中,内存字典的大小可能会对性能产生怎样的影响?是否会因为存储大量结果而导致内存溢出?
记忆化搜索通过存储已经计算过的表达式和它们的结果来减少重复计算,从而提高算法的效率。然而,这种做法确实会增加内存使用量,因为每个不同的子表达式及其结果都会被存储在内存中。在处理较大或复杂的表达式时,内存字典的大小可能会显著增长,潜在地导致较高的内存消耗。如果内存资源有限,存储大量的结果可能会导致内存溢出或性能下降。在实际应用中,需要根据可用资源和表达式的复杂度来权衡记忆化的使用。
🦆
在递归分治算法中,每次递归调用时如何处理可能发生的整数溢出问题?
在递归分治算法中,整数溢出是一个需要关注的问题,尤其是在处理包含大数运算的表达式时。一种常见的解决策略是使用编程语言提供的大数支持库(如 Python 中的 int 类型自动处理大数问题)。对于不支持大数的语言,可以采用特定的数据结构或库来处理大数运算,以避免溢出。此外,递归函数在进行运算前也可以进行溢出检测,如检查操作数是否超过了数据类型的安全范围,并相应地调整或报错。

相关问题

不同的二叉搜索树 II

给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。

 

示例 1:

输入:n = 3
输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]

示例 2:

输入:n = 1
输出:[[1]]

 

提示:

  • 1 <= n <= 8

基本计算器

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()

 

示例 1:

输入:s = "1 + 1"
输出:2

示例 2:

输入:s = " 2-1 + 2 "
输出:3

示例 3:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

 

提示:

  • 1 <= s.length <= 3 * 105
  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式
  • '+' 不能用作一元运算(例如, "+1" 和 "+(2 + 3)" 无效)
  • '-' 可以用作一元运算(即 "-1" 和 "-(2 + 3)" 是有效的)
  • 输入中不存在两个连续的操作符
  • 每个数字和运行的计算将适合于一个有符号的 32位 整数

给表达式添加运算符

给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target ,在 num 的数字之间添加 二元 运算符(不是一元)+- 或 * ,返回 所有 能够得到 target 的表达式。

注意,返回表达式中的操作数 不应该 包含前导零。

 

示例 1:

输入: num = "123", target = 6
输出: ["1+2+3", "1*2*3"] 
解释: “1*2*3” 和 “1+2+3” 的值都是6。

示例 2:

输入: num = "232", target = 8
输出: ["2*3+2", "2+3*2"]
解释: “2*3+2” 和 “2+3*2” 的值都是8。

示例 3:

输入: num = "3456237490", target = 9191
输出: []
解释: 表达式 “3456237490” 无法得到 9191 。

 

提示:

  • 1 <= num.length <= 10
  • num 仅含数字
  • -231 <= target <= 231 - 1