当前位置:   article > 正文

华为OD机试 - 提取字符串中的最长合法简单数学表达式(Java & JS & Python & C)_提取字符串中的最长数学表达式

提取字符串中的最长数学表达式

题目描述

提取字符串中的最长合法简单数学表达式,字符串长度最长的,并计算表达式的值。如果没有,则返回 0 。

简单数学表达式只能包含以下内容:

  • 0-9数字,符号+-*

说明:

  1. 所有数字,计算结果都不超过long
  2. 如果有多个长度一样的,请返回第一个表达式的结果
  3. 数学表达式,必须是最长的,合法的
  4. 操作符不能连续出现,如 +--+1 是不合法的

输入描述

字符串

输出描述

表达式值

用例

输入1-2abcd
输出-1
说明最长合法简单数学表达式是"1-2",结果是-1

题目解析

注意!!!本题原题描述中没有 / 除号

因此,本题的合法表达式不需要考虑 '/' 号,也就不用考虑除0,以及除法是整除还是小数除的问题。

另外,本题的 +、-号仅作为运算符号,不作为正负号。因此 "+1","-1" 这种不能理解为合法的表达式。


本题可以分为两步求解:

  1. 找出输入串中最长合法的表达式
  2. 计算最长合法表达式的结果

关于1的求解,有两种思路:

  • 双指针
  • 正则匹配

其中正则匹配实现起来比较简单,用于匹配合法表达式的正则也不是很难写,对应正则解析如下:

对于python而言,为了更好地适配findall方法,我们可以对上面正则表达式中内层括号使用到非捕获组


关于2的求解

对于JS和Python而言,可以使用内置的eval函数计算字符串表达式的结果。

更常规的思路是利用栈结构:

找出最长合法表达式子串后,比如 "1-2*3+10+2",我们需要注意表达式运算符优先级问题,即先乘,后加减,相同优先级的运算从左到右进行。

这里我的思路是将 合法表达式串 进行分块,比如上面表达式可以分为:

  • 1
  • -2*3
  • 10
  • 2

我们可以发现:

  • +、-运算符前面的操作数都是独立成块,比如上面表达式的操作数1,10
  • * 运算符前面的操作数则需要组合成块,比如上面表达式的操作数2后面是 * 运算符,因此 2 需要和 3 进行组合。

分块之后,我们只需要求各块结果之和即可。

具体逻辑实现如下:

  • 首先定义一个栈stack,用于保存各个块的结果;
  • 其次定义一个块的值容器numStr,用于临时缓存分块的值;
  • 最后定义一个块的系数变量numCoef,用于临时缓存分块的系数;

扫描合法表达式串,如果当前扫描的字符c是:

  • c 是数字,则直接缓存进块的值容器numStr中
  • c 是 + 号,则打断前一个操作数的收集,此时我们应该将前一个操作数计算结果后加入到stack中,操作数 = int(numStr) * numCoef,同时更新numCoef = 1,因为c是+号,所以后一个操作数的系数为1
  • c 是 - 号,则打断前一个操作数的收集,此时我们应该将前一个操作数计算结果后加入到stack中,操作数 = int(numStr) * numCoef,同时更新numCoef = -1,因为c是-号,所以后一个操作数的系数为-1
  • c 是 * 号,则打断前一个操作数的收集,并且 * 前后的两个操作数需要组合,而 * 前面的操作数记录在stack栈顶中,我们可以取出stack栈顶值 记录到 numCoef 中,即 * 前面的操作数,可以当初 * 后面操作数的系数

这块实现的更详细解析,可以参考:

华为OD机试 - 符号运算(Java & JS & Python & C)_java 华为od机试,符号运算-CSDN博客


2024.01.29

本题按照上面解法,有考友反馈只能拿15%通过率,实在想不通是哪里还有问题,感觉上面思路已经很完美了。

想了一个晚上,唯一可能的就是合法表达式的判断,本题没有直接说明合法表达式的限制规则,只是说:

  • 简单数学表达式只能包含:0-9数字,符号+-*
  • 操作符不能连续出现,如 +--+1 是不合法的

其他的限制就没有了。

按照上面解法的正则匹配

如果输入串是:"+1+2m2222"

则该正则会依次匹配出合法表达式"1+2" 和 ”2222“,由于2222的长度更长,因此最长合法表达式是"2222",计算结果也是2222。

但是,我们根据题目对合法表达式的限制规则来看,其实:"+1+2" 也是符合要求的:

  • 该表达式中只能包含:0-9数字,符号+-*
  • 该表达式中没有出现连续操作符

那要是这样说的话,如果输入串是:"+1+2+m22222"

那么 "+1+2+" 是不是也算符合题目要求的合法表达式呢。。。。。

我滴个乖,这个出题真是太不严谨了。。。。

我更新了下面解法,让其可以完成 "+1+2" 的合法表达式识别,但是我不认为  "+1+2+" 是合法表达式,所以没有适配。

网上搜了一圈,找到一个100%的python解法,但是感觉不对,有需要的可以找我要。

JS算法源码

正则+栈解法
  1. const rl = require("readline").createInterface({ input: process.stdin });
  2. var iter = rl[Symbol.asyncIterator]();
  3. const readline = async () => (await iter.next()).value;
  4. void (async function () {
  5. const s = await readline();
  6. console.log(getResult(s));
  7. })();
  8. function getResult(s) {
  9. const maxLenExp = getMaxLenExp(s);
  10. if (maxLenExp.length == 0) {
  11. return 0;
  12. } else {
  13. return calcExpStr(maxLenExp);
  14. }
  15. }
  16. function getMaxLenExp(s) {
  17. // 下面正则无法匹配这样的数字串:+1+2
  18. // const reg = /((\d+[+*-])*\d+)/g;
  19. // 下面正则可以匹配到这样的数字串:+1+2
  20. const reg = /([+-]?(\d+[+*-])*\d+)/g;
  21. let maxLenExp = "";
  22. let res;
  23. while ((res = reg.exec(s)) != null) {
  24. const exp = res[0];
  25. if (exp.length > maxLenExp.length) {
  26. maxLenExp = exp;
  27. }
  28. }
  29. return maxLenExp;
  30. }
  31. function calcExpStr(exp) {
  32. // 这里在表达式结尾追加"+0"是为了避免后面收尾操作,不理解的话,可以去掉此步,测试下"1-2"
  33. exp += "+0";
  34. // 记录表达式中各块的操作数
  35. const stack = [];
  36. // 各块操作数的"值"部分的缓存容器
  37. let numStr = [];
  38. // 各块操作数的"系数"部分,默认为1
  39. let num_coef = 1;
  40. // 如果合法的表达式可以+或-开头
  41. const start = exp[0];
  42. if (start == "+" || start == "-") {
  43. // 将exp开头的符号去掉
  44. exp = exp.slice(1);
  45. }
  46. if (start == "-") {
  47. // 如果表达式开头是负号,则开头操作数的系数为-1
  48. num_coef = -1;
  49. }
  50. // 处理剩余表达式
  51. for (let c of exp) {
  52. if (c >= "0" && c <= "9") {
  53. numStr.push(c);
  54. continue;
  55. }
  56. // 如果扫描到的字符c是运算符,那么该运算符打断了前面操作数的扫描,前面操作数 = 系数 * 值
  57. const num = num_coef * parseInt(numStr.join(""));
  58. stack.push(num);
  59. // 清空缓存容器,用于下一个操作数的”值“记录
  60. numStr = [];
  61. switch (c) {
  62. case "+":
  63. // 如果运算符是加法,则后一个操作数的系数为1
  64. num_coef = 1;
  65. break;
  66. case "-":
  67. // 如果运算符是减法,则后一个操作数的系数为-1
  68. num_coef = -1;
  69. break;
  70. case "*":
  71. // 如果运算符是乘法,则后一个操作数的系数为栈顶值,比如2*3,其中2可以当作3的系数
  72. num_coef = stack.pop();
  73. break;
  74. }
  75. }
  76. // 表达式分块后,每一块独立计算,所有块的和就是表达式的结果
  77. let res = 0;
  78. for (let num of stack) {
  79. res += num;
  80. }
  81. return res;
  82. }
正则+eval解法
  1. const rl = require("readline").createInterface({ input: process.stdin });
  2. var iter = rl[Symbol.asyncIterator]();
  3. const readline = async () => (await iter.next()).value;
  4. void (async function () {
  5. const s = await readline();
  6. console.log(getResult(s));
  7. })();
  8. function getResult(s) {
  9. const maxLenExp = getMaxLenExp(s);
  10. if (maxLenExp.length == 0) {
  11. return 0;
  12. } else {
  13. return eval(maxLenExp);
  14. }
  15. }
  16. function getMaxLenExp(s) {
  17. // 下面正则无法匹配这样的数字串:+1+2
  18. // const reg = /((\d+[+*-])*\d+)/g;
  19. // 下面正则可以匹配到这样的数字串:+1+2
  20. const reg = /([+-]?(\d+[+*-])*\d+)/g;
  21. let maxLenExp = "";
  22. let res;
  23. while ((res = reg.exec(s)) != null) {
  24. const exp = res[0];
  25. if (exp.length > maxLenExp.length) {
  26. maxLenExp = exp;
  27. }
  28. }
  29. return maxLenExp;
  30. }

Java算法源码

正则+栈解法
  1. import java.util.LinkedList;
  2. import java.util.Scanner;
  3. import java.util.regex.Matcher;
  4. import java.util.regex.Pattern;
  5. public class Main {
  6. public static void main(String[] args) {
  7. Scanner sc = new Scanner(System.in);
  8. System.out.println(getResult(sc.nextLine()));
  9. }
  10. public static long getResult(String s) {
  11. String maxLenExp = getMaxLenExp(s);
  12. if (maxLenExp.length() == 0) {
  13. return 0;
  14. } else {
  15. return calcExpStr(maxLenExp);
  16. }
  17. }
  18. public static String getMaxLenExp(String s) {
  19. // 下面正则无法匹配这样的数字串:+1+2
  20. // Matcher matcher = Pattern.compile("((\\d+[+*-])*\\d+)").matcher(s);
  21. // 下面正则可以匹配到这样的数字串:+1+2
  22. Matcher matcher = Pattern.compile("([+-]?(\\d+[+*-])*\\d+)").matcher(s);
  23. String maxLenExp = "";
  24. while (matcher.find()) {
  25. String exp = matcher.group(0);
  26. if (exp.length() > maxLenExp.length()) {
  27. maxLenExp = exp;
  28. }
  29. }
  30. return maxLenExp;
  31. }
  32. public static long calcExpStr(String exp) {
  33. // 这里在表达式结尾追加"+0"是为了避免后面收尾操作,不理解的话,可以去掉此步,测试下"1-2"
  34. exp += "+0";
  35. // 记录表达式中各块的操作数
  36. LinkedList<Long> stack = new LinkedList<>();
  37. // 各块操作数的"值"部分的缓存容器
  38. StringBuilder numStr = new StringBuilder();
  39. // 各块操作数的"系数"部分,开头的操作数系数默认为1
  40. long num_coef = 1;
  41. // 如果合法的表达式可以+或-开头
  42. char start = exp.charAt(0);
  43. if (start == '+' || start == '-') {
  44. // 将exp开头的符号去掉
  45. exp = exp.substring(1);
  46. }
  47. if (start == '-') {
  48. // 如果表达式开头是负号,则开头操作数的系数为-1
  49. num_coef = -1;
  50. }
  51. // 处理剩余表达式
  52. for (int i = 0; i < exp.length(); i++) {
  53. char c = exp.charAt(i);
  54. if (c >= '0' && c <= '9') {
  55. numStr.append(c);
  56. continue;
  57. }
  58. // 如果扫描到的字符c是运算符,那么该运算符打断了前面操作数的扫描,前面操作数 = 系数 * 值
  59. long num = num_coef * Long.parseLong(numStr.toString());
  60. stack.add(num);
  61. // 清空缓存容器,用于下一个操作数的”值“记录
  62. numStr = new StringBuilder();
  63. switch (c) {
  64. case '+':
  65. // 如果运算符是加法,则后一个操作数的系数为1
  66. num_coef = 1;
  67. break;
  68. case '-':
  69. // 如果运算符是减法,则后一个操作数的系数为-1
  70. num_coef = -1;
  71. break;
  72. case '*':
  73. // 如果运算符是乘法,则后一个操作数的系数为栈顶值,比如2*3,其中2可以当作3的系数
  74. num_coef = stack.removeLast();
  75. break;
  76. }
  77. }
  78. // 表达式分块后,每一块独立计算,所有块的和就是表达式的结果
  79. long res = 0;
  80. for (long num : stack) {
  81. res += num;
  82. }
  83. return res;
  84. }
  85. }

Python算法源码

正则+栈解法
  1. # 输入获取
  2. import re
  3. s = input()
  4. # 计算合法表达式的结果
  5. def calcExpStr(exp):
  6. # 这里在表达式结尾追加"+0"是为了避免后面收尾操作,不理解的话,可以去掉此步,测试下"1-2"
  7. exp += '+0'
  8. # 记录表达式中各块的操作数
  9. stack = []
  10. # 各块操作数的"值"部分的缓存容器
  11. numStr = []
  12. # 各块操作数的"系数"部分,默认为1
  13. num_coef = 1
  14. # 如果合法的表达式可以+或-开头
  15. start = exp[0]
  16. if start == '+' or start == '-':
  17. # 将exp开头的符号去掉
  18. exp = exp[1:]
  19. if start == '-':
  20. # 如果表达式开头是负号,则开头操作数的系数为-1
  21. num_coef = -1
  22. # 处理剩余表达式
  23. for c in exp:
  24. if '9' >= c >= '0':
  25. numStr.append(c)
  26. continue
  27. # 如果扫描到的字符c是运算符,那么该运算符打断了前面操作数的扫描,前面操作数 = 系数 * 值
  28. num = num_coef * int("".join(numStr))
  29. stack.append(num)
  30. # 清空缓存容器,用于下一个操作数的”值“记录
  31. numStr.clear()
  32. if c == '+':
  33. # 如果运算符是加法,则后一个操作数的系数为1
  34. num_coef = 1
  35. elif c == '-':
  36. # 如果运算符是减法,则后一个操作数的系数为-1
  37. num_coef = -1
  38. elif c == '*':
  39. # 如果运算符是乘法,则后一个操作数的系数为栈顶值,比如2*3,其中2可以当作3的系数
  40. num_coef = stack.pop()
  41. # 表达式分块后,每一块独立计算,所有块的和就是表达式的结果
  42. return sum(stack)
  43. # 获取最长合法表达式
  44. def getMaxLenExp():
  45. # 下面正则无法匹配这样的数字串:+1+2
  46. # lst = re.compile(r"((?:\d+[+*-])*\d+)").findall(s)
  47. # 下面正则可以匹配到这样的数字串:+1+2
  48. lst = re.compile(r"([+-]?(?:\d+[+*-])*\d+)").findall(s)
  49. maxLenExp = ""
  50. for exp in lst:
  51. if len(exp) > len(maxLenExp):
  52. maxLenExp = exp
  53. return maxLenExp
  54. # 算法入口
  55. def getResult():
  56. maxLenExp = getMaxLenExp()
  57. if len(maxLenExp) == 0:
  58. return 0
  59. else:
  60. return calcExpStr(maxLenExp)
  61. # 算法调用
  62. print(getResult())
正则+eval解法
  1. # 输入获取
  2. import re
  3. s = input()
  4. # 获取最长合法表达式
  5. def getMaxLenExp():
  6. # 下面正则无法匹配这样的数字串:+1+2
  7. # lst = re.compile(r"((?:\d+[+*-])*\d+)").findall(s)
  8. # 下面正则可以匹配到这样的数字串:+1+2
  9. lst = re.compile(r"([+-]?(?:\d+[+*-])*\d+)").findall(s)
  10. maxLenExp = ""
  11. for exp in lst:
  12. if len(exp) > len(maxLenExp):
  13. maxLenExp = exp
  14. return maxLenExp
  15. # 算法入口
  16. def getResult():
  17. maxLenExp = getMaxLenExp()
  18. if len(maxLenExp) == 0:
  19. return 0
  20. else:
  21. return eval(maxLenExp)
  22. # 算法调用
  23. print(getResult())

C算法源码

双指针+栈解法
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #define MAX_SIZE 10000
  5. #define OPERATOR_NUM_LEN 100
  6. #define OPERATOR_NUM_SIZE 1000
  7. char s[MAX_SIZE] = {'\0'};
  8. long calcExpStr(const char *exp) {
  9. // 记录表达式中各块的操作数
  10. long stack[OPERATOR_NUM_SIZE];
  11. int stack_size = 0;
  12. // 各块操作数的"值"部分的缓存容器
  13. char numStr[OPERATOR_NUM_LEN] = {'\0'};
  14. int numStr_size = 0;
  15. // 各块操作数的"系数"部分,默认为1
  16. long num_coef = 1;
  17. // 如果合法的表达式可以+或-开头
  18. char start = exp[0];
  19. if(start == '+' || start == '-') {
  20. // 将exp开头的符号去掉
  21. exp += 1;
  22. }
  23. if(start == '-') {
  24. // 如果表达式开头是负号,则开头操作数的系数为-1
  25. num_coef = -1;
  26. }
  27. int i = 0;
  28. while (exp[i] != '\0') {
  29. char c = exp[i];
  30. if (c >= '0' && c <= '9') {
  31. numStr[numStr_size++] = c;
  32. } else {
  33. // 如果扫描到的字符c是运算符,那么该运算符打断了前面操作数的扫描,前面操作数 = 系数 * 值
  34. long num = num_coef * atol(numStr);
  35. stack[stack_size++] = num;
  36. // 清空缓存容器,用于下一个操作数的”值“记录
  37. numStr_size = 0;
  38. memset(numStr, '\0', OPERATOR_NUM_LEN);
  39. if (c == '+') {
  40. // 如果运算符是加法,则后一个操作数的系数为1
  41. num_coef = 1;
  42. } else if (c == '-') {
  43. // 如果运算符是减法,则后一个操作数的系数为-1
  44. num_coef = -1;
  45. } else if (c == '*') {
  46. // 如果运算符是乘法,则后一个操作数的系数为栈顶值,比如2*3,其中2可以当作3的系数
  47. num_coef = stack[--stack_size];
  48. }
  49. }
  50. i++;
  51. }
  52. // 收尾处理
  53. if (numStr_size > 0) {
  54. long num = num_coef * atol(numStr);
  55. stack[stack_size++] = num;
  56. }
  57. // 表达式分块后,每一块独立计算,所有块的和就是表达式的结果
  58. long res = 0;
  59. for (int j = 0; j < stack_size; j++) {
  60. res += stack[j];
  61. }
  62. return res;
  63. }
  64. int isDigit(char c) {
  65. return c >= '0' && c <= '9';
  66. }
  67. int isOperator(char c) {
  68. return c == '+' || c == '-' || c == '*';
  69. }
  70. int main() {
  71. gets(s);
  72. // 记录最长合法表达式的长度
  73. int maxLen = 0;
  74. // 记录最长合法表达式的起始位置
  75. int start = -1;
  76. // 双指针找最长合法表达式
  77. int l = 0;
  78. int r = 0;
  79. while (s[r] != '\0') {
  80. // 合法表达式必须以数字开头
  81. // 如果+-开头的表达式也是合法的表达式,则需要添加当前代码135~138行逻辑
  82. while (s[l] != '\0' && !isDigit(s[l])) {
  83. l++;
  84. }
  85. if (s[l] == '\0') {
  86. break;
  87. }
  88. // l找到合法表达式的开头后,r指针扫开始描合法表达式剩余部门
  89. r = l + 1;
  90. while (s[r] != '\0') {
  91. // 如果r指针扫描到的是数字字符,则继续扫描
  92. // 如果r指针扫描到的是+-*符号,则需要看r-1字符是不是+-*符号,如果不是则继续扫描
  93. if (isDigit(s[r]) || (isOperator(s[r]) && !isOperator(s[r - 1]))) {
  94. r++;
  95. } else {
  96. // 其他情况中断r扫描
  97. break;
  98. }
  99. }
  100. // 此时 [l, r) 左闭右开范围就是合法表达式
  101. int len = r - l;
  102. // 注意如果r-1位置是+-*符号,则不能包含
  103. if (isOperator(s[r - 1])) {
  104. len -= 1;
  105. }
  106. // 如果认为 +1+2,-2*3 这种也是合法的表达式,则需要加入下面逻辑
  107. if (l >= 1 && (s[l - 1] == '+' || s[l - 1] == '-')) {
  108. l--;
  109. len++;
  110. }
  111. // 记录最长的合法表达式长度,以及起始位置
  112. if (len > maxLen) {
  113. maxLen = len;
  114. start = l;
  115. }
  116. // 找到一个合法表达式后,继续找下一个合法表达式
  117. l = r + 1;
  118. }
  119. if (start == -1) {
  120. // 如果没找到合法表达式,则直接打印0
  121. puts("0");
  122. } else {
  123. // 找到最长合法表达式,则计算其结果打印
  124. s[start + maxLen] = '\0';
  125. printf("%ld\n", calcExpStr(s + start));
  126. }
  127. return 0;
  128. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/173431
推荐阅读
相关标签
  

闽ICP备14008679号