这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情
问题描述
给定一个表示分数加减运算的字符串 expression ,你需要返回一个字符串形式的计算结果。
这个结果应该是不可约分的分数,即最简分数。 如果最终结果是一个整数,例如 2,你需要将它转换成分数形式,其分母为 1。所以在上述例子中, 2 应该被转换为 2/1。
示例 1:
输入: expression = "-1/2+1/2"
输出: "0/1"
示例 2:
输入: expression = "-1/2+1/2+1/3"
输出: "1/3"
示例 3:
输入: expression = "1/3-1/2"
输出: "-1/6"
提示:
- 输入和输出字符串只包含 '0' 到 '9' 的数字,以及 '/', '+' 和 '-'。
- 输入和输出分数格式均为 ± 分子/分母。如果输入的第一个分数或者输出的分数是正数,则 '+' 会被省略掉。
- 输入只包含合法的最简分数,每个分数的分子与分母的范围是 [1,10]。 如果分母是 1,意味着这个分数实际上是一个整数。
- 输入的分数个数范围是 [1,10]。
- 最终结果的分子与分母保证是 32 位整数范围内的有效整数。
思路分析
首先我们先要理解一下题目意思,题目会给我们一个表示分数加减运算的字符串 expression,我们需要对给出的式子进行解析运算,这其实就是要我们模拟一遍我们小学学过的分数运算来进行解题,我们可以分为以下几步来解答题目:
1、提取字符串中的运算符和分数
我们需要先将字符串中的运算符和分数分离提取出来,这里我们需要注意:如果输入的第一个分数或者输出的分数是正数,则 '+' 会被省略掉,也就是说如果第一个分数不是正数的话我们需要在前面补上一个+运算符,使运算符和分数可以一一对应起来。
- 提取字符串中的分数字符
获取字符串中所有分数值我们可以直接使用正则表达式加split函数进行分割:
const nums = expression.split(/\+|\-/);
if (nums[0] == "") nums.shift();
- 提取字符串中的运算符
我们可以直接遍历一遍字符串,将遇到的运算符按顺序记录起来即可:
const flag = [];
if (expression[0] != "-") {
flag.push("+");
}
let str = "";
for (let i = 0; i < expression.length; i++) {
if (["+", "-"].includes(expression[i])) {
flag.push(expression[i]);
}
}
- 遍历同时提取运算符和分数
我们也可以不使用正则表达式来提取分数,我们可以再遍历提取运算符的同时将分数也提取出来
const nums = [];
const flag = [];
if (!["+", "-"].includes(expression[0])) {
flag.push("+");
}
let str = "";
for (let i = 0; i < expression.length; i++) {
if (["+", "-"].includes(expression[i])) {
flag.push(expression[i]);
nums.push(str);
str = "";
} else {
str += expression[i];
}
}
if (str) nums.push(str);
if (nums[0] == "") nums.shift();
2、计算两个分数之和
提取出运算符和分数之后,我们便可以开始进行计算了,分数计算是我们小学学过的知识了,这里我们可以回忆一下,计算过程主要分为以下几个步骤:
- (1)分数通分
对于两个不同分母的分数,我们应该先对两个分数进行通分,通分其实就是要找到两个分母的最小公倍数,所以我们需要先获取两个分母的最小公倍数,最小公倍数我们可以通过最小公约数来快速求得,代码如下:
//获取两数的最大公约数
const gcd = (a, b) => {
return a % b == 0 ? b : gcd(b, a % b);
};
//获取两数的最小公倍数
const getLcm = (a, b) => {
return (a * b) / gcd(a, b);
};
- (2)分子相加 获取到最小公倍数之后,我们需要可以将两个分数的分母都变为最小公倍数,分子也应进行对应的变化,最后将两个分子相加作为新的分子,最小公倍数作为新的分母即可,具体代码如下:
//获取最小公倍数
const denominator = getLcm(a[1], b[1]);
//通分后的分子1
const moleculeA = (a[0] * denominator) / a[1];
//通分后的分子2
const moleculeB = (b[0] * denominator) / b[1];
let tmp = 0;
//将通分后的分子相加
tmp += moleculeA * (fA == "-" ? -1 : 1);
tmp += moleculeB * (fB == "-" ? -1 : 1);
- (3)分数约分
获得相加后的分数之后,我们还需要对新分数进行约分,约分其实就是找出分子和分母的最大公约数,同时除于最大公约数即可:
//获取最大公约数
const div = gcd(Math.abs(tmp), denominator);
//分子分母同时除于最大公约数
return (tmp > 0 ? "+" : "") + tmp / div + "/" + denominator / div;
遍历计算整个式子的和
let res = 0;
for (let i = 0; i < nums.length; i++) {
res = calculate(res, flag[i] + nums[i]);
}
完整 AC 代码如下:
AC 代码
/**
* @param {string} expression
* @return {string}
*/
var fractionAddition = function (expression) {
const nums = [];
const flag = [];
if (!["+", "-"].includes(expression[0])) {
flag.push("+");
}
let str = "";
for (let i = 0; i < expression.length; i++) {
if (["+", "-"].includes(expression[i])) {
flag.push(expression[i]);
nums.push(str);
str = "";
} else {
str += expression[i];
}
}
if (str) nums.push(str);
if (nums[0] == "") nums.shift();
let res = 0;
const gcd = (a, b) => {
return a % b == 0 ? b : gcd(b, a % b);
};
const getLcm = (a, b) => {
return (a * b) / gcd(a, b);
};
const calculate = (a, b) => {
if (a == 0) return b;
if (b == 0) return a;
const fA = a[0],
fB = b[0];
a = a.slice(1).split("/");
b = b.slice(1).split("/");
const denominator = getLcm(a[1], b[1]);
const moleculeA = (a[0] * denominator) / a[1];
const moleculeB = (b[0] * denominator) / b[1];
let tmp = 0;
tmp += moleculeA * (fA == "-" ? -1 : 1);
tmp += moleculeB * (fB == "-" ? -1 : 1);
const div = gcd(Math.abs(tmp), denominator);
return (tmp > 0 ? "+" : "") + tmp / div + "/" + denominator / div;
};
for (let i = 0; i < nums.length; i++) {
res = calculate(res, flag[i] + nums[i]);
}
return res[0] == "+" ? res.slice(1) : res;
};
说在后面
本人为算法业余爱好者,平时只是随着兴趣偶尔刷刷题,如果上面分享有错误的地方,欢迎指出,感激不尽。