之前在面试的过程中经常会遇到匹配括号的问题,比如下面这类题目:
- 编写一个函数,该函数接收一个算术表达式作为参数,返回括号缺失的位置。
2.3 + 23 / 12 + (3.14 * 0.24
- 实现一个normalize函数,能将特定的字符串转化为特定的结构化数据。
[a[b[c]]]
// 转化为
{ value: 'a', children: { value: 'b', children: { value: 'c' } } }
偷偷告诉你,第2题是阿里的面试题(这也太难了吧!)
之前看到这类的题目也是一脸懵逼,没有半点思路,然后有一天我在《数据结构与算法JavaScript描述》书中了解到通过栈可以用来判断一个表达式中括号是否匹配。
栈
什么是栈?
栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。
咖啡厅内的一摞盘子是现实世界中常见的栈的例子。只能从最上面取盘子,盘子洗净后,也只能摞在这一摞盘子的最上面。栈被称为一种后入先出(LIFO,last-in-first-out)的数据结构。
对栈的两种主要操作是将一个元素压入栈和将一个元素弹出栈。入栈使用 push() 方法,出栈使用 pop() 方法。图 演示了入栈和出栈的过程。
其实这些感念挺抽象的,慢慢理解吧。
实战
好了,我们先来做一个题,判断回文:回文是指这样一种现象:一个单词、短语或数字,从前往后写和从后往前写都是一样的。
单词“dad”、“racecar”就是回文
数字 1001 也是回文
function isPalindrome(word) {
let s = [];
for (let i = 0; i < word.length; i++) {
s.push(word[i]); // 入栈
}
let rword = "";
while (s.length > 0) {
rword += s.pop(); // 出栈
}
if (word == rword) {
return true;
}
else {
return false;
}
}
栈的特点就是先进后出,把字符添加到数组中的时候是正序,而取出来的时候是倒序,正是因为这种先进后出刚好相反的顺序,就可以判断回文了。
如果数字123,那么入栈是1、2、3,出栈是3、2、1,入栈和出栈得到的结果不一样,那么他们就不是回文。
如果字符dad,那么入栈是d、a、d,出栈是d、a、d,入栈和出栈得到的结果是一样,那么他们就是回文。
现在已经理解的栈的特点了吧。。。
前面有两道题目,第一题判断缺少的括号可以这么写,参考答案:
function demo (str) {
let sign = '(){}[]'
let s = []
for (let i = 0; i < str.length; i++) {
if (sign.includes(str[i])) {
let val = str[i];
switch (val) {
case '(':
case '[':
case '{': s.push(val); break;
case ')':
let map1 = s.pop();
if (map1 !== '(') {
return `位置${i}的)不匹配`
}
break;
case ']':
let map2 = s.pop();
if (map2 !== '[') {
return `位置${i}的]不匹配`
}
break;
case '}':
let map3 = s.pop();
if (map3 !== '{') {
return `位置${i}的}不匹配`
}
break;
}
}
}
if (s.length) {
return `符号${s.join()}没有闭合`
} else {
return '符号正确'
}
}
我们的括号包括(){}[]
,然后入栈是左边部分的括号,出栈是右边部分的括号,当两边的括号有对应关系的时候,说明括号匹配是正确的。
然后下面是第二题[a[b[c]]]
转换为树结构的答案:
function normalize (str) {
let s = [];
let list = [];
let obj = {}
for (let i = 0; i < str.length; i++) {
let value = str[i]
switch (value) {
case '[':
s.push(i)
break;
case ']':
let leftIndex = s.pop();
list.unshift([leftIndex, i])
default:
break;
}
}
let [start, end] = list[0]
let parent = obj
for (let i = 1; i < list.length; i++) {
let [a, b] = list[i];
let result = str.slice(start + 1, a) + str.slice(b + 1, end);
start = a;
end = b;
parent.value = result;
parent.children = {};
parent = parent.children;
}
let [x, y] = list[list.length - 1]
parent.value = str.slice(x + 1, y)
return obj
}
先匹配了括号在字符串中的位置保存在数组list
中[ [ 0, 8 ], [ 2, 7 ], [ 4, 6 ] ]
,然后通过区间的范围可以知道左右两边的内容(0到2和7到8是第一个括号里面的内容,依次类推,注意最后的内容是4到6这个是闭合的关系),然后通过字符串截取的方式可以得到。最后通过对象之前的引用关系转换为树状结构。