前言
下一个更大元素 I
原题链接
nums1
中数字 x
的 下一个更大元素 是指 x
在 nums2
中对应位置 右侧 的 第一个 比 x
大的元素。给你两个 没有重复元素 的数组 nums1
和nums2
,下标从 0 开始计数,其中nums1
是 nums2
的子集。对于每个 0 <= i < nums1.length
,找出满足 nums1[i] == nums2[j]
的下标 j
,并且在 nums2
确定 nums2[j]
的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length
的数组 ans
作为答案,满足 ans[i]
是如上所述的 下一个更大元素 。
示例 1:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1
中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,
nums2 = [1,3,4,2]
。不存在下一个更大元素,所以答案是 -1 。 - 1 ,用加粗斜体标识,
nums2 = [1,3,4,2]
。下一个更大元素是 3 。 - 2 ,用加粗斜体标识,
nums2 = [1,3,4,2]
。不存在下一个更大元素,所以答案是 -1 。
我们先来整理一下题干:
- 在
nums2
数组中首先要找一个元素要满足nums1[i] == nums2[j]
。 - 再找一个元素它的下标要 >
j
,并满足对应元素值 >nums1[i]
。
1.1 暴力法
代码怎么写?我们先不去考虑最优的代码解,遇事不决,暴力破解。我们来看看:
- 首先,结果数组长度随着
nums1
。那么就有int[] res = new int[nums1.length]
- 其次不存在更大元素的时候,答案是 - 1。存在的时候,就是对应元素值。那么初始化动作就有
Arrays.fill(res, -1);
- 我们要找到一个下标,满足
nums1[i] == nums2[j]
,得到坐标sameValIndex
。再去nums2
数组中寻找下标大于sameValIndex
,并且值大于nums1
的即是我们想要的结果。
那么可得暴力解法:
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len = nums1.length;
int[] res = new int[len];
// 初始化默认值
Arrays.fill(res, -1);
for (int i = 0; i < len; i++) {
// 赋值最大值,避免在满足nums1[i] == nums2[j]这个条件之前,就进入了第二个if条件的判断。
// 因为sameValIndex它的值,只有在找到nums1[i] == nums2[j]对应节点的情况下才有意义。
int sameValIndex = Integer.MAX_VALUE;
for (int j = 0; j < nums2.length; j++) {
// 找到值相等的节点下标
if (nums1[i] == nums2[j]) {
sameValIndex = j;
}
// 在nums2数组,并且在sameValIndex下标之后,寻找第一个更大的元素,找到则退出循环
if (j > sameValIndex && nums2[j] > nums1[i]) {
res[i] = nums2[j];
break;
}
}
}
return res;
}
1.2 单调栈
上面的暴力法有什么问题?每次for
循环都要完整的遍历一遍nums2
数组。这样做真的有必要吗?
我们换一个思路,首先我们求得的元素都是在nums2
当中找到的。那么这里我们是不是可以先无视nums1
数组,以nums2
数组当的每一个元素为基准,找到后面第一个比它大的元素。那这样就不需要每次for
循环都遍历一遍nums2
数组了。也就是说我们做个映射表,这样的操作我们只用一次即可。
- 寻找下一个比当前元素大的,我们就应该使用单调递增栈。
- 寻找下一个比当前元素小的,我们就应该使用单调递减栈。
映射表的创建:
// 单调递增栈
LinkedList<Integer> stack = new LinkedList<>();
// 映射关系表,Key:某个元素,Value:nums2中下一个比它大的元素值
HashMap<Integer, Integer> map = new HashMap<>();
// 记录Map,每个元素和它的下一个更大的元素
for (int num : nums2) {
// 如果当前元素比栈顶还要大,说明对于栈顶元素来说,当前元素就是下一个比它大的。当然坐标肯定比它大,因为我们是正序遍历
while (!stack.isEmpty() && stack.peek() < num) {
// 出栈的同时,记录映射关系
map.put(stack.pop(), num);
}
// 入栈
stack.push(num);
}
// 上面for循环,我们必定满足每一个元素都只入栈一次。但是无法保证所有元素都出栈成功,因此剩下来的元素都是找不到比它大的,我们给他出栈并赋值-1即可
while (!stack.isEmpty()) {
map.put(stack.pop(), -1);
}
那么最终结果,我们只需要一个for
循环,遍历nums1
即可:
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len = nums1.length;
int[] res = new int[len];
// 单调递增栈
LinkedList<Integer> stack = new LinkedList<>();
// 映射关系表,Key:某个元素,Value:nums2中下一个比它大的元素值
HashMap<Integer, Integer> map = new HashMap<>();
// 记录Map,每个元素和它的下一个更大的元素
for (int num : nums2) {
// 如果当前元素比栈顶还要大,说明对于栈顶元素来说,当前元素就是下一个比它大的。当然坐标肯定比它大,因为我们是正序遍历
while (!stack.isEmpty() && stack.peek() < num) {
// 出栈的同时,记录映射关系
map.put(stack.pop(), num);
}
// 入栈
stack.push(num);
}
// 上面for循环,我们必定满足每一个元素都只入栈一次。但是无法保证所有元素都出栈成功,因此剩下来的元素都是找不到比它大的,我们给他出栈并赋值-1即可
while (!stack.isEmpty()) {
map.put(stack.pop(), -1);
}
// 遍历nums1,找到对应元素在映射表中,下一个更大的值
for (int i = 0; i < len; i++) {
res[i] = map.get(nums1[i]);
}
return res;
}