题目:给定一个数组(不含重复值),找到每一个位置i的左边和右边离i最近且值比arr[i]小的位置,返回所有值。
举例:有数组 arr = {3,4,1,5,6,2,7};那么返回的信息就是[{-1,2},{0,2},{-1,-1},{2,5},{3,5},{2,-1},{5,-1}]
。这里返回的是二维数组。-1表示不存在。 进阶问题:若arr可能有重复值,找到每个位置i左边和右边的离i最近的且比arr[i]小的位置。
要求:arr长度为n,实现原问题和进阶问题的解法,时间复杂度为O(N)。
先看暴力解法,思路较简单,T(n) = O(n*n),每个位置分别向左和向右遍历一下,总可以确定。代码如下:
1 | public static int[][] rightway(int[] arr) { |
单调栈结构解决原问题:
- 1,准备一个栈,记为stack
,栈中放数组的位置,开始stack为空,如果找到每一个i位置左边和右边离i位置最近的且比arr[i]小的位置,那么需要让stack从栈顶到栈底的位置所代表的值是严格递减的;如果找到每一个i位置左边和右边离i位置最近且值比arr[i]大的位置,那么需要让stack从栈顶到栈底的位置所代表的值是严格递增的。我们讨论前者,实际上这两种情况原理一样。
下面举例说明这个问题:初始时arr={3,4,1,5,6,2,7};stack从栈顶到栈底为{}(意思是为空)。
- 1,遍历到arr[0] = 3,stack为空,直接放入0位置。stack从栈顶到栈底为{0位置(值为3)};
- 2,遍历到arr[1] = 4,发现直接放入1位置不会破坏stack要求的特性,直接放入1位置。stack从栈顶到栈底为{1位置(值是4)、0位置(值为3)};
- 2,遍历到arr[2] = 1,发现直接放入2位置会破坏stack要求的特性,从stack开始弹出位置,如果x位置被弹出,在栈中位于x下面的的位置,就是x位置左边离x位置最近且比arr[x]小的位置;当前遍历到的位置就是x位置右边离x位置最近且比arr[x]小的位置。从stack中弹出1,在栈中位于1位置下面的是位置0,当前遍历到的位置是2,所以ans[1] = {0,2}。弹出1位置后,发现放入2位置(值是1)还会破坏规则,所以继续弹出位置,这次弹出位置0。在栈中位于位置0下面已经没有位置了,说明在位置0左边不存在比arr[0]小的值,当前遍历到的位置是位置2,所以ans[0] = {-1,2}。stack已经为空,所以放入2位置(值为1),stack从栈顶到栈底为:{2位置{值是1}};….
以此类推,得到ans[4] = {3,5},ans[3] = {2,5},ans[6] = {5,-1},ans[5] = {2,-1},ans[2] = {-1,-1}。
简单证明单调栈中,如果x位置被弹出,在栈中位于x位置下面的位置为什么就是x位置左边离x位置最近且比arr[x]小的位置
- 答:如果x位置被弹出,在栈中位于x位置下面的位置为什么就是x位置左边离x位置最近且值比arr[x]小的位置;当前遍历到的位置就是x位置右边离x位置最近且比arr[x]小的位置。假设stack当前栈顶位置是x,值是5;x下面是i位置,值是1;当前遍历到j位置,值是4。整个栈是没有重复值的。当前遍历到j位置,但是x位置已经在栈中,所以x位置肯定在j位置的左边。如果5和4之间存在小于5的数,那么没等遍历到当前的4,x位置(值是5)就已经被弹出了,轮不到当前的4来让x位置的5弹出,所以5和4之间的数要么没有,要么一定比5大,所以x位置右边离x位置最近且小于arr[x]的位置就是j位置。
- 当前弹出的是x位置,x位置下面是位置i,i比x早进栈,所以i的位置肯定在x位置的左边;如果在1和5之间存在小于1的数,那么i位置(值为1)会被提前弹出,在栈中i位置和x位置就不可能贴在一起,如果在1和5之间存在大于1但小于5的数,那么在栈中i位置和x位置之间一定会夹上一个别的位置,也不可能贴在一起,所以1和5之间的数要么没有,要么一定大于5,那么x位置左边离x位置最近且小于arr[x]的位置就是i位置。
证明结束,整个流程中,每个位置都进栈一次、出栈一次,所以整个流程的时间复杂度是O(n),看如下的getNearLessNoRepeat方法。
1 | class Solution { |
进阶问题,可能含有重复的数组使用单调栈,其实过程和原问题的解法差不多。全过程看如下代码:
1 |