962. 最大宽度坡
给定一个整数数组 A,坡是元组 (i, j),其中 i < j 且 A[i] <= A[j]。这样的坡的宽度为 j - i。
找出 A 中的坡的最大宽度,如果不存在,返回 0 。
示例 1:
输入:[6,0,8,2,1,5] 输出:4 解释: 最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5.
示例 2:
输入:[9,8,1,0,1,9,4,0,4,1] 输出:7 解释: 最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1.
提示:
- 2 <= A.length <= 50000
- 0 <= A[i] <= 50000
思路:
暴力:O(n2)
方法1:双关键字排序
方法2:维护递减序列(单调栈)+二分
方法3:单调栈+ 倒序遍历
方法4:树状数组
方法5:线性,维护前缀最小值和后缀最大值 + 双指针
思路:
方法1:双关键字排序
目的是找满足A[i] < A[j]的最小的i,故可考虑将下标连同该下标上的数一同排个序。
排序方法是第一关键字按照数字大小从小到大,第二关键字是按照下标从小到大。
这样遍历到当前下标时,位于该下标上的数字就是其在原数组中的下标,只需维护一个最小值就能更新最大坡宽。
时间复杂度:O(nlogn)
class Solution {public int maxWidthRamp(int[] nums) {int n = nums.length;Integer[] a = new Integer[n];for (int i = 0; i < n; i++)a[i] = i;Arrays.sort(a, (o1, o2) -> (nums[o1] == nums[o2] ? o1 - o2 : nums[o1] - nums[o2]));int res = 0, minIdx = a[0];for (int x : a) {res = Math.max(x - minIdx, res);minIdx = Math.min(minIdx, x);}return res;}}
方法2:单调栈 + 二分
维护一个严格单调递减序列,遍历到当前元素时,既能通过单调递减栈弹栈的方式找左侧第一个大于(等于)该元素的值/下标, 也能通过对单调递减序列二分的方式找最左侧小于(等于)该元素的值/下标。
时间复杂度:O(nlogn)
class Solution {public int maxWidthRamp(int[] nums) {int n = nums.length;int[] stk = new int[n];int p = -1, res = 0;for (int i = 0; i < n; i++) {int x = nums[i];int l = 0, r = p;while (l < r) {int mid = l + r >> 1;if (nums[stk[mid]] > x)l = mid + 1;elser = mid;}if (nums[stk[l]] <= x)res = Math.max(res, i - stk[l]);if (p == -1 || nums[i] < nums[stk[p]])stk[++p] = i;}return res;}}
方法3:单调栈 + 倒序遍历
从前往后遍历生成一个严格单调递减序列,倒序考虑当前栈顶下标对应的值是否小于当前值,如果是,更新答案,弹栈并继续判断栈顶下标对应的值是否小于当前值,如果不是,继续处理下一个数。
时间复杂度:O(n)
class Solution {public int maxWidthRamp(int[] nums) {int n = nums.length;int[] stk = new int[n];int p = -1, res = 0;for (int i = 0; i < n; i++)if (p < 0 || nums[i] < nums[stk[p]])stk[++p] = i;for (int i = n - 1; i >= 0 && p > -1; i--) {while (p > -1 && nums[i] >= nums[stk[p]]) {res = Math.max(i - stk[p--], res);}}return res;}}
方法4:树状数组:又学到一种树状数组的用法
由于本题数组长度与数值范围一致,故可用树状数组解决。
从前往后遍历,依据当前元素更新:在树状数组中找到小于等于当前元素值出现的最小下标。
class Solution {int N = 50010;public int maxWidthRamp(int[] nums) {int res = 0;BIT bit = new BIT(N);for (int i = 0; i < nums.length; i++) {int x = nums[i] + 1;res = Math.max(res, i - bit.min(x));bit.add(x, i);}return res;}class BIT {int[] a;int n;BIT(int n) {this.n = n;a = new int[n + 1];Arrays.fill(a, n + 1);}void add(int idx, int x) {for (int i = idx; i <= n; i += i & (-i)) {a[i] = Math.min(a[i], x);}}int min(int idx) {int res = n + 1;for (int i = idx; i > 0; i -= i & (-i)) {res = Math.min(a[i], res);}return res;}}}
1124. 表现良好的最长时间段
给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
示例 1:
输入:hours = [9,9,6,0,6,6,9] 输出:3 解释:最长的表现良好时间段是 [9,9,6]。
示例 2:
输入:hours = [6,6,6] 输出:0
提示:
1 <= hours.length <= 1040 <= hours[i] <= 16
思路:
错误方法:前缀和 + 二分答案
本题不满足二分性
例 8 10 6 16 5,大于8表示满足要求用1表示,否则不满足用-1表示。其前缀和数组为-1 0 -1 0 -1
如果用二分,第一次mid = (0+4+1) / 2 = 2, 没有满足要求的(没有连续2天能使劳累天数大于非劳累天数),故r = mid - 1 = 1
第二次mid = (0 + 1 + 1) / 2 = 1, 有满足要求的,故l = mid = 1
第三次退出循环。结果输出1,但是应该是3才对。
方法1:
前缀和 + 哈希 + 贪心
大于8表示满足要求用1表示,否则不满足用-1表示。
- 计算前缀和- 加入 `0 = -1`到哈希表方便计算- 遍历数组,找到哈希表中第一个小于当前元素的第一个元素的下标(**越长越好,先有-1,才会有-2,故只需在哈希表中查找比当前元素小一的元素即可**)- 若哈希表中不存在当前元素,存入当前元素及其下标,否则不存入(**贪心**)。
class Solution {public int longestWPI(int[] hours) {Map<Integer, Integer> map = new HashMap<>();int sum = 0, res = 0;for (int i = 0; i < hours.length; i++) {hours[i] = hours[i] > 8 ? 1 : -1;sum += hours[i];if (sum > 0)res = Math.max(res, i + 1);else {if (map.containsKey(sum - 1))res = Math.max(res, i - map.get(sum - 1));if (!map.containsKey(sum))map.put(sum, i);}}return res;}}
方法2:单调栈 + 贪心- 计算前缀和- 遍历前缀和数组,记录一个单调递减栈- 从后往前贪心,能弹栈就弹栈
class Solution {public int longestWPI(int[] hours) {Deque<Integer> stack = new LinkedList<>();int n = hours.length;int[] a = new int[n + 1];for (int i = 0; i < hours.length; i++) {hours[i] = hours[i] > 8 ? 1 : -1;a[i + 1] = a[i] + hours[i];}for (int i = 0; i <= n; i++) {if (stack.isEmpty() || a[stack.peek()] > a[i])stack.push(i);}int res = 0;for (int i = n; i >= 0; i--) {if (stack.isEmpty()) break;while (!stack.isEmpty() && a[i] > a[stack.peek()]) {int l = stack.pop();res = Math.max(res, i - l);}}return res;}}
其它例题:
AcWing 4487. 最长连续子序列
