船长表情包镇楼
栈
栈是⼀种“先进后出”(FILO, First In Last Out)的数据结构
栈适合解决什么问题?
- 处理具有完全包含关系的问题(匹配问题、函数调用关系、树与递归等)
LeetCode肝题
- 面试题 03.04. 化栈为队
// 使用两个栈实现一个队列的功能
var MyQueue = function() {
this.s1 = []
this.s2 = []
};
MyQueue.prototype.push = function(x) {
this.s2.push(x)
};
MyQueue.prototype.pop = function() {
if (this.s1.length) {
return this.s1.pop()
}
let length = this.s2.length
for(let i = 0; i < length; i++) {
this.s1.push(this.s2.pop())
}
return this.s1.pop()
};
MyQueue.prototype.peek = function() {
if (this.s1.length) {
return this.s1[this.s1.length - 1]
}
let length = this.s2.length
for(let i = 0; i < length; i++) {
this.s1.push(this.s2.pop())
}
return this.s1[this.s1.length - 1]
};
MyQueue.prototype.empty = function() {
return this.s1.length === 0 && this.s2.length === 0
};
-
- 棒球比赛
// 按照流程处理栈顶元素,最后遍历求和
var calPoints = function(ops) {
var arr = []
for(let i = 0; i < ops.length; i++) {
switch(ops[i]){
case '+':
const ret = arr.pop(), sum = arr[arr.length - 1] + ret
arr.push(ret)
arr.push(sum)
break
case 'D':
arr.push(arr[arr.length - 1] * 2)
break
case 'C':
arr.pop()
break
default:
arr.push(parseInt(ops[i]))
}
}
let sum = 0
for(let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
};
-
- 比较含退格的字符串
// 定义一个处理字符串的方法,判断字符类型如果是#栈弹出,否则插入
var transform = function(str, arr) {
for(let i = 0; i < str.length; i++) {
if (str[i] == '#' && arr.length > 0) arr.pop()
else if (str[i] != '#') arr.push(str[i])
}
}
var backspaceCompare = function(S, T) {
let arr1 = [], arr2 = []
transform(S, arr1)
transform(T, arr2)
if (arr1.length != arr2.length) return false
for(let i = 0; i < arr2.length; i++) {
if (arr2[i] != arr1[i]) return false
}
return true
};
-
- 验证栈序列
// 定位到pushed的元素与popped第一个元素相等的位置,将匹配的值弹出,如果最终为空,则能验证栈序列
var validateStackSequences = function(pushed, popped) {
let arr = []
for(let i = 0, j = 0; i < popped.length; i++) {
arr.push(pushed[i])
while(arr.length && arr[arr.length - 1] == popped[j]) {
arr.pop()
j++
}
}
return arr.length == 0
};
-
- 有效的括号
// 定义一个对象存放对应关系,将字符依次存入栈内,如果匹配上,则弹出栈顶元素,最终如果栈为空则是有效的括号
var isValid = function(s) {
const mapObj = {
'(': ')',
'[': ']',
'{': '}'
}
const arr = []
for (let i = 0; i< s.length; i++) {
if (s[i] in mapObj) {
arr.push(s[i])
} else {
if (s[i] != mapObj[arr.pop()]) {
arr.push(s[i])
}
}
}
return !arr.length
};
-
- 删除最外层的括号
// 通过对括号计数可以去掉最外层的括号
var removeOuterParentheses = function(S) {
let str = '', num = 0
for (let i = 0; i < S.length; i++) {
if (S[i] == '(' && num++ > 0) str += S[i]
if (S[i] == ')'&& --num > 0) str += S[i]
}
return str
};
-
- 移除无效的括号
// 定义arr存储左括号下标,遇到匹配的右括号则弹出,无法匹配的右括号下标存到delArr里
// 连接两个数组,将s里把下标对应的值删除
var minRemoveToMakeValid = function(s) {
let arr = [], delArr = []
for(let i = 0; i<s.length; i++) {
if (s[i] == '(') {
arr.push(i)
} else if (s[i] == ')') {
if (arr.length){
arr.pop()
} else {
delArr.push(i)
}
}
}
const filter = arr.concat(delArr), filterArr = [...s]
for(let i = 0; i<filter.length; i++) {
filterArr[filter[i]] = ''
}
return filterArr.join('')
};
-
- 二叉树的后序遍历
// 定义一个执行栈arr,定义一个状态栈process,根据不同的状态执行不同的操作
var postorderTraversal = function(root) {
if (!root) return []
let arr = [root], process = [0], ret = []
while(arr.length > 0) {
switch(process.pop()) {
case 0:
process.push(1)
if (arr[arr.length-1].left) {
arr.push(arr[arr.length-1].left)
process.push(0)
}
break
case 1:
process.push(2)
if (arr[arr.length-1].right) {
arr.push(arr[arr.length-1].right)
process.push(0)
}
break
case 2:
ret.push(arr[arr.length-1].val)
arr.pop()
break
}
}
return ret
};
-
- 验证二叉树的前序序列化
// 在遍历preorder的时候通过判断如果是数字加##则合并成一个#,最终如果只剩一个#就可以验证该序列
var isValidSerialization = function(preorder) {
if (preorder == '#') return true
let arr = [], tree = preorder.split(',')
for(let i = 0; i < tree.length; i++) {
arr.push(tree[i])
if (arr[0] == '#' && arr.length == 1) return false
while(arr.length > 2 && arr[arr.length - 1] == '#' && arr[arr.length - 2] == '#' && arr[arr.length - 3] != '#') {
arr.pop()
arr.pop()
arr.pop()
arr.push('#')
}
}
return arr[0] == '#' && arr.length == 1
};
-
- 基本计算器 II
// 设定一个优先级,+-为1,*/为2,小技巧是增加一个标识符在最后优先级最低表示结束
// 设定一个数字栈和操作符栈,在遍历字符串表达式s的时候如果当前操作符的优先级小于操作符栈的栈顶元素,则执行计算操作
// 计算操作是数字栈的两个栈顶元素和操作符栈的一个栈顶元素计算
var level = function(val) {
switch (val) {
case '+':
case '-': return 1
case '*':
case '/': return 2
case '@': return -1
}
return 0
}
var calc = function(a, b, ops) {
switch (ops) {
case '+': return a + b
case '-': return a - b
case '*': return a * b
case '/': return parseInt(a / b)
}
}
var calculate = function(s) {
let num = [], ops = []
s += '@'
for(let i = 0, n = 0; i < s.length; i++) {
if (s[i] == ' ') continue
if (level(s[i]) == 0) {
n = n * 10 + parseInt(s[i])
continue
}
num.push(n)
n = 0
while(ops.length > 0 && level(s[i]) <= level(ops[ops.length - 1])) {
let b = num.pop()
let a = num.pop()
num.push(calc(a,b,ops.pop()))
}
ops.push(s[i])
}
console.log(num)
return num[0]
};
-
- 函数的独占时间
// 将id作为结果集的下标,根据状态判断
// 如果状态是start,id对应的值等于当前时间 - pre,pre等于上一次end时候的时间 + 1
// 如果状态是edn,id对应的值等于当前时间 - pre + 1,pre等于上一次start时候的时间 + 1
var exclusiveTime = function(n, logs) {
let ans = Array(n).fill(0), ids = []
for (let i = 0, pre = 0; i < logs.length; i++) {
let arr = logs[i].split(':')
if (arr[1] == 'start') {
if (ids.length > 0) {
ans[ids[ids.length - 1]] += parseInt(arr[2]) - pre
}
ids.push(parseInt(arr[0]))
pre = parseInt(arr[2])
} else {
ans[arr[0]] += parseInt(arr[2]) - pre +1
pre = parseInt(arr[2]) + 1
ids.pop()
}
}
return ans
};
-
- 表现良好的最长时间段
// 将工作时间表转化,大于8的记1,小于8的记-1,然后求出前缀和数组preSum
// 「表现良好时间段」的最大长度实际上也是preSum两个位置的值差大于0并且位置的下标差最大
// 定义一个stack存储preSum所有值小于0的下标,减去一个负数更容易大于0
// 遍历preSum,找到所有大于stack里对应值的下标,寻找下标差值的最大值
var longestWPI = function(hours) {
let preSum = new Array(hours.length + 1).fill(0), stack = [0], max = 0
for(let i = 0; i < hours.length; i++) {
if (hours[i] > 8) preSum[i+1] = preSum[i] + 1
else preSum[i+1] = preSum[i] - 1
}
for(let i = 1; i < preSum.length; i++) {
if (preSum[i] < preSum[stack[stack.length-1]]) stack.push(i)
}
for(let i = preSum.length - 1; i > max; i--) {
while(stack.length && preSum[stack[stack.length-1]] < preSum[i]) {
max = Math.max(max, i - stack.pop())
}
}
return max
};