刷力扣时,遇到关键词:下一个更大/小的数这类题目时,往往会采用单调栈的解法,如每日温度
刷题最常见的问题就是,看到题解,感觉很精妙,但下次遇到一模一样的题目时,往往知道思路,但写不出代码,有或者遇到类似的变体题目时,不会往这方面想。这两种情况在之前的文章(数据结构算法小结)中提到过,分别有两方面的原因:
- 对工具(如单调栈知识点)的特性(适用范围)不明朗
- 对工具的原理没有真正的理解
初学单调栈最容易搞错的一点是,获取下一个更大的数,往往需要用下降栈,下一个更小的数却要用上升栈。
本篇将会从单调栈的原理特性入手,分析单调栈原理和用法,并给出固定的方法模板。
单调栈场景分析
如果一上手就结合问题讲单调栈怎么用,注意力往往在解法上,而容易忽视单调栈的特性,这里先以单调上升栈为例,分析进出栈的特性:
假设原数组为[5,1,6,2,0,7]
,从左向右入栈,维护一个单调上升栈,过程如图所示:
可以发现,单调上升栈更适合维护下一个更小值的数组,若让其维护下一个更大值的数组,过程中便会发现,无法对已出栈的数据进行比较,如果再加一个栈保存出栈数据,计算的复杂度又会变成O(n^2)
,变没有达到优化的效果。
小结
根据上面例子做个小结:
单调栈适用问题
- 获取某个值的下一个更大/更小值
上升栈还是下降栈
- 获取下一个更小值:维护一个上升栈
- 获取下一个更大值:维护一个下降栈
什么时候往下一个更大/更小数组中塞入值:
- 数据出栈时
单调栈问题的模板
根据上面的例子,不难总结出单调栈问题的模板:
// 模板:
const fillArr = (arr, isNextLowerArr) => {
const stack = [];
const getTop = () => stack[stack.length - 1];
// isNextLowerArr为true -> 维护[下一个更小]数组 -> 上升栈
const validFunc = isNextLowerArr
? ((topVal, compareVal) => compareVal >= topVal)
: ((topVal, compareVal) => compareVal <= topVal)
const addData = (data) => {
let top = getTop();
// stack不为空,且顶部值不符合上升/下降规则时,顶部出栈,并填充数组
while(top && !validFunc(top.val, data.val)) {
arr[top.index] = data;
stack.pop();
top = getTop();
}
stack.push(data);
}
addData.getStack = ()=> stack;
return addData;
}
// test:
const testArr = [5,1,6,2,0,7];
const nextLowerArr = Array(testArr.length).fill(null);
const addDataToIncreaseStack = fillArr(nextLowerArr, true);
// 同理,获取下一个更大数组,获取添加数据函数就是fillArr(nextUpperArr, false)
testArr.forEach((val, index) => {
const data = {val, index};
addDataToIncreaseStack(data);
console.log("stack:", [...addDataToIncreaseStack.getStack()]);
console.log("nextLowerArr:", [...nextLowerArr]);
})
后话
明确模板之后,可自行尝试每日温度这一题。
很多时候,比起掌握方法,更进一步总结出模板无疑更有效率,下次遇到同样问题的时候,只需要将复杂问题拆解成多个子问题,分别套模板就行,就和做项目时调用第三方库一样;如果只是感性的掌握解法,解决问题时一点失误都会导致结果出错。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。