跳到主内容

【阿里算法题】原子数量 - Javascript版本

题目描述

给你一个字符串化学式 formula ,返回 每种原子的数量

原子总是以一个大写字母开始,接着跟随 0 个或任意个小写字母,表示原子的名字。

如果数量大于 1,原子后会跟着数字表示原子的数量。如果数量等于 1 则不会跟数字。

  • 例如,"H2O""H2O2" 是可行的,但 "H1O2" 这个表达是不可行的。

两个化学式连在一起可以构成新的化学式。

  • 例如 "H2O2He3Mg4" 也是化学式。

由括号括起的化学式并佐以数字(可选择性添加)也是化学式。

  • 例如 "(H2O2)""(H2O2)3" 是化学式。

返回所有原子的数量,格式为:第一个(按字典序)原子的名字,跟着它的数量(如果数量大于 1),然后是第二个原子的名字(按字典序),跟着它的数量(如果数量大于 1),以此类推。

示例 1:

输入:formula = "H2O"
输出:"H2O"
解释:原子的数量是 {'H': 2, 'O': 1}

示例 2:

输入:formula = "Mg(OH)2"
输出:"H2MgO2"
解释:原子的数量是 {'H': 2, 'Mg': 1, 'O': 2}

示例 3:

输入:formula = "K4(ON(SO3)2)2"
输出:"K4N2O14S4"
解释:原子的数量是 {'K': 4, 'N': 2, 'O': 14, 'S': 4}

解题思路

看到括号表达式,第一反应就是应用栈操作;方题解压入栈的每一层是一个哈希表,每一层计算出原子的数量,遇到右括号后获取倍数值,乘以倍数后弹出最上层,数据并入次上层;

个人习惯只用一层,连带左括号一起压入栈,遇到右括号时一直弹出顶层,直到第一个左括号时停止,获取这一段的原子和数字,将倍数计入括号内的表达式数字,再压入栈中,继续读取后面的字符,重复操作直到结束。

化学表达式有很明显的字符串特征,第一反应使用正则表达式解析原子和数字。 正则表达式 /([A-Z][a-z]?)(\d\*)/g

小技巧:正则表达式带有全局修饰符时,循环调用 exec 方法,方法内部会记录每次执行的索引,结果和 match 全局的正则表达式类似,不过循环 exec 可以获取每次匹配到的分组数据,而前者只有匹配结果。

const formulaReg = /([A-Z][a-z]?)(\d\*)/g;
let RegArr;
while ((RegArr = formulaReg.exec("C4H6CaO4"))) {
console.log(RegArr);
}

这道题跟解析数学运算表达式很像,其实题目不难,思路比较朴素,细心一点处理就好了。

附上代码:

/**
* @param {string} formula
* @return {string}
*/
var countOfAtoms = function (formula) {
const stack = [];
const formulaMap = {};
const formulaArr = formula.split("");
const length = formulaArr.length;
let formulaTmp = "";

function genFormulaMap(formulaStr, times = 1) {
const formulaReg = /([A-Z][a-z]?)(\d*)/g;
let RegArr;
while ((RegArr = formulaReg.exec(formulaStr))) {
const atom = RegArr[1];
const num = Number(RegArr[2]) || 1;
if (formulaMap[atom] === undefined) {
formulaMap[atom] = num * times;
} else {
formulaMap[atom] += num * times;
}
}
}

function push2Stack(formulaStr, times = 1) {
const formulaReg = /([A-Z][a-z]?)(\d*)/g;
let RegArr;
while ((RegArr = formulaReg.exec(formulaStr))) {
const atom = RegArr[1];
const num = Number(RegArr[2]) || 1;
stack.push(`${atom}${num * times}`);
}
}

for (let i = 0; i < length; ++i) {
const item = formulaArr[i];
if (item === ")") {
let str = "";
while (str !== "(") {
formulaTmp = str + formulaTmp;
str = stack.pop();
}
let formulaNum = "";
let j;
for (j = i + 1; j < length && /\d/.test(formulaArr[j]); ++j) {
formulaNum += formulaArr[j];
}
i = j - 1;
if (stack.length > 0) {
push2Stack(formulaTmp, Number(formulaNum) || 1);
formulaTmp = "";
} else {
genFormulaMap(formulaTmp, Number(formulaNum) || 1);
formulaTmp = "";
}
} else if (item === "(" || stack.length > 0) {
stack.push(item);
if (formulaTmp !== "") {
genFormulaMap(formulaTmp);
formulaTmp = "";
}
} else {
formulaTmp += item;
}
}
if (formulaTmp !== "") {
genFormulaMap(formulaTmp);
formulaTmp = "";
}
return Object.entries(formulaMap)
.sort()
.map((x) => (x[1] === 1 ? x[0] : x.join("")))
.join("");
};