前言
最近确实对栈结构着魔了,因为栈的应用确实蛮多的。在html Token构建dom树的时候使用了栈结构,就连Vue中收集watcher对象也是使用了栈的操作,足以可见栈结构的业务能力,特别是在具有先进先出、以及对称特点的方面应用广泛,所以灵活掌握栈结构至关重要!
下一个更大的数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 。
思路分析
题目很啰嗦,反正大概读下来就是,左数组的元素是右数组元素的子元素,需要找到左数组的每个元素对应在右数组的下一个最大值,如果存在,就是目标值,如果不存在,那么就是-1
我当初一看到题,这不是很简单嘛?刷刷刷的就是暴力解法。先遍历nums1的每个元素,找到在nums2对应的下标,从该下标位置开始,再次遍历nums2,直到找到最大值,否则没有最大值,赋值为-1.
代码
var nextGreaterElement = function(nums1, nums2) {
let res = Array(nums1.length).fill(-1) //结果全部赋值为-1
for(let i = 0; i < nums1.length; i++) {
let index = nums2.indexOf(nums1[i]) //找到该下标
for(let j = index ; j < nums2.length; j++ ) {
if(nums2[j] > nums1[i]) {
res[i] = nums2[j]
break;
}
}
}
return res
};
面试的时候,这不是香喷喷?直接写完,简简单单。
然后面试官又发话了:我觉得不优雅,你还有其他方法吗?
我心想,肯定不能说没有啊,那不得行.不过好在早有准备,前几天刚刚刷完有效的括号(传送门),正愁着没有地方复习不是?拿主题练练
思路进阶-单调栈
我们需要注意到,如果对这种对顺序有一定的要求的时候,一般都可以使用单调栈解决。此处单调栈意图还有点不明显,但是实际上也是一个顺序的问题,这里也是绕了一下路。
首先分析,nums1是nums2的子元素,那么实际上,给nums1的每个元素找下一个最大值,实际上就是给nums2当中的某些元素找最大值。我们的res结果数组,就应该初始化为num1.length长度的数组;时应该对nums2当中的元素进行进栈出栈的比较操作。而找寻nums1的下一个最大值,就需要通过关系映射到nums2数组当中,这就需要用到hash表的知识。js中有个天然的Hash表--Map对象,我们就是使用Map对象来表示num1与num2的映射关系。而这道题目就被我们拆分成了单调栈 + Hash表
步骤
- 初始化栈stack、res = Array(nums1.length).fill(-1)
- 遍历nums2数组的每个元素,使用单调栈记录下一个最大值,同时存储映射关系
- 再次遍历num1数组的每个元素,从map对象中获取映射关系
- 返回res目标数组
代码
var nextGreaterElement = function(nums1, nums2) {
let len = nums2.length
let stack = []
let map = new Map(); //天然表
for(let i = 0; i < len; i++) {
while(stack.length && nums2[i] > nums2[stack[stack.length - 1]]) {
let index = stack.pop()
map.set(nums2[index], nums2[i]) //栈顶元素 当前元素,注意当前元素是更大值
}
stack.push(i);
}
let res = [];
for (let j = 0; j < nums1.length; j++) {
res[j] = map.get(nums1[j]) || -1; //获取当前元素,对应nums2数组的元素下一个更大值
}
return res;
};
效果杠杠滴!还算中规中矩!
就这单调栈的用法,确实香喷喷的,于是我又来了一题!
下一个更大的数II
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
示例 1:
输入: nums = [1,2,1] 输出: [2,-1,2] 解释: 第一个 1 的下一个更大的数是 2; 数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
思路分析
这个题目同样也是找下一个最大值,但是这个最大值不一样,又夹杂一丝丝别的气息,那就是最后的结果需要通过循环去寻找最大值,原来的题目是遍历一轮就好了。其余基本跟单调栈的操作一致。
那应该怎么控制这个循环呢?很多人又陷入的迷茫。那么我们先把大致的代码写出来。
测试代码
var nextGreaterElements = function(nums) {
let stack = []
let res = Array(nums.length).fill(-1)
stack.push(0)
for(let i = 1; i < nums.length ; i++) {
let num = nums[i]
while(stack.length && num > nums[stack[stack.length - 1]]) {
res[stack[stack.length - 1]] = num
stack.pop()
}
stack.push(i);
}
return res
};
这实际上代码可以运行,且为部分正确的结果,而此处代码不可行的原因就是因为无法循环,下一个最大值在该元素之前,元素res值为-1,判断错误了,所以此处我们解决的问题就是如何循环该数组。
那么有什么方法呢?莫非是在下标到达最后的时候重新对下标赋值?然后重新再来一次循环?但是此处不建议这么做,因为又会出现死循环,根本跳不出来。就算是沿用这个思路,也是窟窿补窟窿还是窟窿。所以这里我们使用又一妙用----求余数
求余数可谓是算法之中的某些关键,能让你茅塞顿开,恍然大悟,最后惊叹:原来还能这么玩?此处也是余数求余法则,我们先让数组遍历两次,对于前一半的循环,也就是整次循环,不作处理,针对后半段,当下标超过原来的数组长度,我们对其求余数,那么此时第二轮循环开启,同时拿到了第一个元素的值,而对于之后的入栈操作,只针对前面一轮,后面一轮的循环不作处理,是不是很妙?我们,上代码!!!
代码
var nextGreaterElements = function(nums) {
let stack = []
let res = Array(nums.length).fill(-1)
stack.push(0)
for(let i = 1; i < nums.length * 2; i++) {
let num = nums[i % nums.length]
while(stack.length && num > nums[stack[stack.length - 1]]) {
res[stack[stack.length - 1]] = num
stack.pop()
}
if(i < nums.length)
stack.push(i);
}
return res
};
总结
栈这玩意,玩玩还是很有意思的,同样类似的题目力扣中还有很多,但是题目是刷不完的,只有总结正确的方法,才能应对无穷无尽的题目。我写文章的目的,不仅仅是为了介绍这些方法,这些题目,同样也是对我自己的一个总结,也是对方法的一个总结,精力有限,题目难以完成,只能以小见大,从一些题目当中挖掘东西!
我是小白,我们今后再整活!