之前在面試的過程中經常會遇到匹配括號的問題,比如下面這類題目:
1.編寫一個函數,該函數接收一個算術表達式作爲參數,返回括號缺失的位置。
2.3 + 23 / 12 + (3.14 * 0.24
2.實現一個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 map = {
'(': ')',
'{': '}',
'[': ']'
}
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這個是閉合的關係),然後通過字符串截取的方式可以得到。最後通過對象之前的引用關係轉換爲樹狀結構。