前面的文章我们练习数十道 动态规划 的题目。相信小伙伴们对于动态规划的题目已经写的 得心应手 了。
还没看过的小伙伴赶快关注在 「动态规划」 集合里查看哦!
接下来我们开启一个新的篇章 ------ 「单调栈」。
单调栈
顾名思义,单调栈是一种 单调的栈(说的跟没说一样!哈哈)。
即:栈内的元素具有单调性。因此可以分为 单调递增的栈 和 单调递减的栈。
有什么用处呢?
最直接的作用就是 :能够在 O ( N ) O(N) O(N) 的时间复杂度下,返回所有元素中任意一个数左和右两边 距离最近 的首个比该数大或小的位置在哪。(暴力解法 O ( n ∗ n ) O(n*n) O(n∗n))。
求解比该数 大 的位置就需要 单调递减的栈 ;
求解比该数 小 的位置就需要 单调递增的栈 。
如上图:
比 4 大 的左右首个元素是 7 和 8 。(使用 单减栈 解决)
比 4 小 的左右首个元素是 2 和 1 。(使用 单增栈 解决)
实现思路
下面我们以寻找两侧距离最近的且 大于 某元素,即 单调递减的栈 为例(栈底大 -> 栈顶小):
栈的含义: 栈中存放的是按序排列且暂时没有求出结果的元素的 下标 。
维护单调性: 栈内元素严格递减,新来到的元素只需要和栈顶元素进行比较。
新元素的处理: 当一个新元素到来时,先与栈顶元素进行比较。若比栈顶元素小 ,则直接压栈保存;若比栈顶元素大,则需要弹出栈顶元素,直到栈空或比栈顶元素小后压栈。
弹出时更新信息: 当弹出栈顶元素时,此时次栈顶元素和新来元素就是所求的左右两侧距离最近的且大于该元素的值,记录其下标等信息 。
时间复杂度: 时间复杂度为 O ( n ) O(n) O(n),因为每个元素最多被压入和弹出栈一次。
其他元素的求解可以按照流程依次模拟一遍。该方法具体证明过程就不再赘述了,感兴趣的小伙伴可以自己列举情况分析哦!
代码
java
public static int[][] getNearLessNoRepeat(int[] arr) {
int n = arr.length;
// n 行 2 列数组,存储 n 个元素左右两侧的下标
int[][] res = new int[n][2];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
int j = stack.pop();
// 记录左侧下标
res[j][0] = stack.isEmpty() ? -1 : stack.peek();
// 记录右侧下标
res[j][1] = i;
}
stack.push(i);
}
// 没有新来元素后,栈不为空,依次弹出
while (!stack.isEmpty()) {
int j = stack.pop();
res[j][0] = stack.isEmpty() ? -1 : stack.peek();
res[j][1] = -1;
}
return res;
}
代码解释
使用 int[n][2]
记录每个元素中左右两侧符合条件的下标值。
从左到右依次遍历元素:
-
当栈不为空,且栈顶元素大于
arr[i]
时,根据单调减的栈的规定,需要弹出栈顶元素并记录该元素的左右符合要求的值。因此,在弹出栈顶元素后,若栈为空,说明左侧 没有比该元素大的值了,返回 -1 。右侧 即当前新到元素下标 i 。 -
当没有新元素后,若栈不为空,依次弹出所有的栈中元素。此时说明,右侧 没有比该元素大的值了,返回 -1;左侧 为次栈顶元素下标。
为了帮助小伙伴更好理解,下面是几种特殊情况的举例。
相信小伙伴这次都能够轻松理解啦 ~
讲到这里,可能有小伙伴提出疑问了,这里说的都是元素不同的情况。那如果遇到 两个相等的元素 时,又该如何应对呢?
下篇文章我们为大家揭晓!敬请期待吧 ~
~ 点赞 ~ 关注 ~ 星标 ~ 不迷路 ~!!!
关注回复「ACM紫书」获取 ACM 算法书籍~