内容来源于自己的刷题笔记,对一些题目进行方法总结,用 java 语言实现。
6. 生成窗口最大值数组:
-
题目描述:
有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如,数组为 [4,3,5,4,3,3,6,7],窗口大小为3时:
[4 3 5] 4 3 3 6 7 窗口中最大值为5
4 [3 5 4] 3 3 6 7 窗口中最大值为5
4 3 [5 4 3] 3 6 7 窗口中最大值为5
4 3 5 [4 3 3] 6 7 窗口中最大值为4
4 3 5 4 [3 3 6] 7 窗口中最大值为6
4 3 5 4 3 [3 6 7] 窗口中最大值为7
如果数组长度为 n,窗口大小为 w,则一共产生 n-w+1 个窗口的最大值。
请实现一个函数:
- 输入:整型数组 arr,窗口大小为 w
- 输出:一个长度为 n-w+1 的数组 res,res[i] 表示每一种窗口状态下的最大值
以本题为例,结果应该返回 [5,5,5,4,6,7]。
-
解题思路:
用双端队列来实现窗口最大值的更新,首先生成双端队列 qmax,qmax 中存储数组中 arr 中的下标。
假设遍历到 arr[i],qmax 的放入规则为:
- 如果 qmax 为空,直接把下标 i 放进 qmax,放入过程结束。
- 如果 qmax 不为空,取出当前 qmax 队尾存放的下标,假设为 j。
- 如果 arr[j] > arr[i],直接把下标 i 放进队尾存放的下标,假设为 j。
- 如果 arr[j] <= arr[i],把 j 从 qmax 中弹出,重复 qmax 的放入规则。
这样就维护了一个长度为 w 的子数组的最大值更新的结构。
-
代码实现:
public static int[] getMaxWindow(int[] arr,int w){ if (arr == null || arr.length <= 0){ return null; } LinkedList<Integer> qmax = new LinkedList<>(); int[] res = new int[arr.length - w + 1]; int index = 0; for (int i = 0;i < arr.length;i++){ //如果队列不为空,并且队尾存储序号的元素小于等于要放入序号的元素,就将队尾序号弹出,重复队列的放入规则 while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]){ qmax.pollLast(); } //队列为空放入队列中,队列不为空但队尾存储序号的元素大于要放入序号的元素,放入队列尾部,两者的逻辑都是一样的 qmax.addLast(i); //队列首部索引过期,直接弹出 if (qmax.peekFirst() == i - w){ qmax.pollFirst(); } //当元素的索引大于等于窗口大小时,可以开始存储窗口数组 if (i >= w - 1){ res[index++] = arr[qmax.peekFirst()]; } } return res; }
7. 单调栈结构:
-
题目描述:
基础问题:给定一个不含有重复值的数组 arr,找到每一个 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] 小的位置。返回所有位置相应的信息。
-
解题思路:
两个问题都可以使用单调栈的思路进行求解:
准备一个栈,记为 stack<Integer>,栈中放的元素是数组的位置,开始时 stack 为空。如果找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 小的位置,那么需要让 stack 从栈顶到栈底的位置所代表的值是严格递减的;如果找到每一个 i 位置左边和右边离 i 位置最近且只比 arr[i] 大的位置,那么需要让 stack 从栈顶到栈底的位置所代表的值是严格递增的。
无重复:在单调栈中,如果 x 位置被弹出,在栈中位于 x 位置下面的位置就是 x 位置离左边 x 位置最近且值比 arr[x] 小的位置;当前遍历到的位置就是 x 位置右边离 x 位置最近且值比 arr[x] 小的位置。假设 stack 当前栈顶位置是 x,值是5;x 下面是 i 位置,值是1;当前遍历到 j 位置,值是4。
如果是有重复的数组,栈中存储的不是单个索引而是链表结构的索引,其他逻辑类似,但此时离左边 x 位置最近且值比 arr[x] 小的位置应该是链表结构中最晚加入的值,即 stack.peek().get(stack.peek().size() - 1)。
-
代码实现:
/** * 不重复的数组 * @param arr * @return */ public 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(); 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; } /** * 重复数组 * @param arr * @return */ public 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); 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(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; }
8. 求最大子矩阵的大小:
-
题目描述:
给定一个整型矩阵 map,其中的值只有0和1两种,求其中全是1的所有矩形区域中,最大的矩形区域为1的数量。
例如:
1 1 1 0
其中,最大的矩阵区域有3个1,所以返回3。
1 0 1 1
1 1 1 1
1 1 1 0
其中,最大的矩阵区域有6个1,所以返回3。
-
解题思路:
- 矩阵的行数为 N,以每一行做切割,统计以当前行作为底的情况下,每个位置往上(包括 j 位置)1的数量。使用高度数组 height 表示。
- 对于每一次切割,都利用更新后的 height 数组来求出当前行为底的情况下,最大的矩形是什么。该步骤类似于在一个直方图内求出最大矩形面积。生成一个栈,记为 stack,从左到右遍历 height 数组,只有当前 i 位置的值 height[i] 大于当前栈顶位置所代表的值(height[stack.peek()]),则 i 位置才可以压入 stack;如果当前 i 位置的值 height[i] 小于或等于当前栈顶位置所代表的值(height[stack.peek()]),则把栈中存的位置不断弹出,直到某一个栈顶所代表的值小于 height[i],再把位置 i 压入,并在此期间进行如下操作:
- 假设当前弹出的栈顶位置记为位置 j,弹出栈顶之后,新的栈顶记为 k。然后开始考虑位置 j 的柱子向右和向左最远能扩到哪里。
- 对位置 j 的柱子来说,向右最远能扩到的位置:如果 height[j] > height[i],那么 i - 1 就是向右能扩到的最远距离;如果 height[j] == height[i],那么 i - 1 位置不一定是向右能扩到的最远距离,只是起码能扩到的位置,但是不要紧,这种情况下,i 位置的柱子向左也必然可以扩到 j 位置。
- 对位置 j 的柱子来说,向左最远能扩到的位置:根据单调栈的性质,k 位置的值是 j 位置的值左边离 j 位置最近的比 j 位置的值小的位置,所以 j 位置的柱子向左最远可以扩到 k + 1 位置。
- 综上所述,j 位置的柱子能扩出来的最大矩形是 (i - k - 1) * height[j]。
-
代码实现:
public int maxRecSize(int[][] map){ if (map == null || map.length == 0 || map[0].length == 0){ return 0; } int maxArea = 0; int[] height = new int[map[0].length]; for (int i = 0;i < map.length;i++){ for (int j = 0;j < map[0].length;j++){ height[j] = map[i][j] == 0 ? 0 : height[j]+1; } maxArea = Math.max(maxRecFromBottom(height),maxArea); } return maxArea; } public int maxRecFromBottom(int[] height){ if (height == null || height.length == 0){ return 0; } int maxArea = 0; Stack<Integer> stack = new Stack<>(); for (int i = 0;i < height.length;i++){ while (!stack.isEmpty() && height[i] <= height[stack.peek()]){ int j = stack.pop(); int k = stack.isEmpty() ? -1 : stack.peek(); int curArea = (i - k -1)*height[j]; maxArea = Math.max(maxArea,curArea); } stack.push(i); } while (!stack.isEmpty()){ int j = stack.pop(); int k = stack.isEmpty() ? -1 : stack.peek(); int curArea = (height.length - k -1)*height[j]; maxArea = Math.max(maxArea,curArea); } return maxArea; }
9. 最大值减去最小值小于或等于 num 的子数组数量:
-
题目描述:
给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况:
max(arr[i…j]) - min(arr[i…j]) <= nummax(arr[i…j]) 表示子数组 arr[i…j] 中的最大值,min(arr[i…j]) 表示子数组 arr[i…j] 中的最小值。
-
解题思路:
首先分析题目中的条件,得出两个结论:
- 如果子数组 max(arr[i…j]) - min(arr[i…j]) <= num,那么 arr[i…j] 中的每一个子数组都满足条件:因为子数组只有可能范围更小或者不变,不可能变大。
- 如果子数组 arr[i…j] 不满足条件,则所有包含 arr[i…j] 的子数组都满足条件:因为包含子数组的数组只有可能范围更大或者不变,不可能变小。
生成两个双端队列 qmax 和 qmin,生成两个整型变量 i 和 j,表示子数组的范围,即 arr[i…j],生成整型变量 res,表示所有满足条件的子数组数量。
首先,j 不断向右移动,如果满足条件持续向右生长,一旦发现不满足,停止生长,此时,arr[i…j]、arr[i…j-1]…arr[i…i],一共 j - i 个子数组符合,此时左边必须是固定的,不然会与后面的计数重复;然后 i 开始生长,直到满足条件停止生长。
-
代码实现:
public static int getNum(int[] arr,int num){ if (arr == null || arr.length == 0 || num < 0){ return 0; } LinkedList<Integer> qmin = new LinkedList<>(); LinkedList<Integer> qmax = new LinkedList<>(); int i = 0; int j = 0; int res = 0; while (i < arr.length){ while (j < arr.length){ if (qmin.isEmpty() || qmin.peekLast() != j){ while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j]){ qmin.pollLast(); } qmin.addLast(j); while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j]){ qmax.pollLast(); } qmax.addLast(j); } if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num){ break; } j++; } res += j - 1; if (qmin.peekFirst() == i){ qmin.pollFirst(); } if (qmax.peekFirst() == i){ qmax.pollFirst(); } i++; } return res; }