想要精通算法和SQL的成长之路 - 下一个更大元素II
前言
一. 下一个更大元素 II
给定一个循环数组 nums
( nums[nums.length - 1]
的下一个元素是 nums[0]
),返回 nums
中每个元素的 下一个更大元素 。
数字 x
的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1
。
示例 1:
- 输入:
nums = [1,2,1]
- 输出: [2,-1,2]
- 解释: 第一个 1 的下一个更大的数是 2;数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
1.1 暴力法
这里的暴力和 下一个更大元素I 很相似。唯一的不同就是:从两个数组变成了一个数组(只不过可以循环)
整体思路:
- 两层
for
循环。 - 由于数组模拟的是循环数组,因此我们在原数组的基础上再拼接一个数组即可。这里可以用
Arrays.copyOf
进行拷贝。
public int[] nextGreaterElements(int[] nums) {
int len = nums.length;
// 因为循环数组的原因,我们用两个循环拼接成一个大数组
int[] tmp = Arrays.copyOf(nums, 2 * len);
// 拷贝后半部分数据
for (int i = len; i < tmp.length; i++) {
tmp[i] = nums[i - len];
}
int[] res = new int[len];
for (int i = 0; i < nums.length; i++) {
// 找不到更大值时的默认值是-1
res[i] = -1;
// 从当前下标往后遍历,找到第一个值更大的
for (int j = i + 1; j < tmp.length - 1; j++) {
if (tmp[j] > nums[i]) {
res[i] = tmp[j];
break;
}
}
}
return res;
}
当然,也可以不需要拷贝数组,通过 下标的一个取模计算 同样能实现循环。
public int[] nextGreaterElements(int[] nums) {
int len = nums.length;
int[] res = new int[len];
for (int i = 0; i < len; i++) {
// 找不到更大值时的默认值是-1
res[i] = -1;
// 从当前下标往后遍历,找到第一个值更大的
for (int j = i + 1; j < 2 * len - 1; j++) {
// 对下标进行取模,就不会越界,同时实现了循环的一个下标计算
int index = j % len;
if (nums[index] > nums[i]) {
res[i] = nums[index];
break;
}
}
}
return res;
}
1.2 单调栈
思路:
- 发现我们要找下一个比当前元素更大的元素。那么我们就使用单调递增栈。
- 默认值可以提前给数组赋值-1。
- 入栈的是数组下标(记得取模处理)。出栈的条件则是:当前元素 > 栈顶元素(栈顶元素最大)。
- 这样每个元素最多遍历两遍,最少一次。可以说是
O(n)
。
最终代码如下:
public int[] nextGreaterElements(int[] nums) {
int len = nums.length;
int[] res = new int[len];
// 赋默认值:-1
Arrays.fill(res, -1);
// 单调栈
LinkedList<Integer> stack = new LinkedList<>();
// 遍历长度:两个数组长度大小
for (int i = 0; i < len * 2; i++) {
// 记得对下标进行取模,否则会越界,因为我们取值来源还是原数组
int index = i % len;
// 如果发现 当前元素 > 栈顶(单调递增),说明 当前元素 就是 栈顶元素 的下一个更大的值
while (!stack.isEmpty() && nums[stack.peek()] < nums[index]) {
// 弹出其下标,赋值当前元素给返回结果
res[stack.pop()] = nums[index];
}
// 下标索引入栈
stack.push(index);
}
return res;
}