前提假设
在数组中想找到一个数,左边和右边比这个数小、且离这个数最近的位置。比如:1 5 4 3 2 8 6, 1的左边最近比它小是无,右边最近比它小是无。5的左边最近比它小是1,右边最近比它小是4, 4的左边最近比它小是1,右边最近比它小是3...
传统的想法是遍历,这样的时间复杂度是O(N^2),那么有没有O(N)的方法呢?这就要用到单调栈结构了。
单调栈解答
当给的数中没有重复值时
相关代码
public static int[][] getNearLessNoRepeat(int[] arr) {
int[][] res = new int[arr.length][2];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
//注意:栈中放的是地址
//如果栈不为空,且栈顶元素大于要加入的元素
while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
int popIndex = stack.pop();
//如果栈不空,那么左边最近最小的就是原栈下一个的元素(当然因为原栈栈顶弹出了,所以就是当前栈顶的元素)
//如果栈是空的,就是没有左边最近最小的元素
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
//0位置储存左边最近最小的地址
//1位置储存右边最近最小的地址
res[popIndex][0] = leftLessIndex;
res[popIndex][1] = i;
}
//栈为空,或者栈顶元素小于要加入的元素
stack.push(i);
}
//最后的清算程序
while (!stack.isEmpty()) {
int popIndex = stack.pop();
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
res[popIndex][0] = leftLessIndex;
//能到清算程序,说明没有要加入的元素了
res[popIndex][1] = -1;
}
return res;
}
当给的数中有重复值时
形成链表时不用int[],因为这个不支持扩展,可以用动态数组ArrayList或者双向链表LinkedList。
即Stack<ArrayList<Integer>> 或者Stack<LinkedList<Integer>>
相关的代码
public static int[][] getNearLess(int[] arr) {
int[][] res = new int[arr.length][2];
Stack<List<Integer>> stack = new Stack<>();
for (int i = 0; i < arr.length; i++) {
while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
List<Integer> popIs = stack.pop();
// 取位于下面位置的列表中,最晚加入的那个
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(
stack.peek().size() - 1);
//比如弹出的可能是很多位置的3,形成的链表,都要计算它的左边和右边
for (Integer popi : popIs) {
res[popi][0] = leftLessIndex;
res[popi][1] = i;
}
}
//栈不为空,但是栈顶所代表元素的值和要加入的元素相同,
// 那么就要将其加入到栈顶的元素的链表中
if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
stack.peek().add(Integer.valueOf(i));
} else {
//栈为空
ArrayList<Integer> list = new ArrayList<>();
list.add(i);
stack.push(list);
}
}
//清算程序
while (!stack.isEmpty()) {
List<Integer> popIs = stack.pop();
// 取位于下面位置的列表中,最晚加入的那个
int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(
stack.peek().size() - 1);
for (Integer popi : popIs) {
res[popi][0] = leftLessIndex;
res[popi][1] = -1;
}
}
return res;
}
时间复杂度
时间复杂度:因为每个数是只进一次出一次,所以是O(N)。
相关题目
定义:数组中累积和与最小值的乘积,假设叫做指标A。 给定一个数组,请返回子数组中,指标A最大的值。
思路解析
可以以数组中的每一位为参考,找到以当前位为最小值,左边界是最近比它小的数是它不能扩到的位置,右边界是最近比它小的数是它不能扩到的位置。就可以转换成单调栈的形式。
相关代码???没读太懂
public static int max2(int[] arr) {
int size = arr.length;
int[] sums = new int[size];
sums[0] = arr[0];
for (int i = 1; i < size; i++) {
sums[i] = sums[i - 1] + arr[i];
}
int max = Integer.MIN_VALUE;
Stack<Integer> stack = new Stack<Integer>();
for (int i = 0; i < size; i++) {
//比要进的数大,就弹出栈
while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
int j = stack.pop();
max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]);
}
stack.push(i);
}
//清算程序
while (!stack.isEmpty()) {
int j = stack.pop();
max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]);
}
return max;
}