NC157 单调栈
题意
给定一个长度为 n 的可能含有重复值的数组 arr ,找到每一个 i 位置左边和右边离 i 位置最近且值比 小的位置。
1. 暴力做法
按照题意,两重循环模拟即可。
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums intvector * @return intvector<vector<>> */ vector<vector<int> > foundMonotoneStack(vector<int>& nums) { int n = nums.size(); vector<vector<int> > res; for (int i = 0; i < n; i++) { int l = -1, r = -1; // 找l的时候正向走,因为肯定是越来越近的,所以只要比Nums[i]小,一定会更新到更近的l值 for (int j = 0; j < i; j++) if (nums[j] < nums[i]) l = j; // 找r的时候反向,同理 for (int j = n-1; j > i; j--) { if (nums[j] < nums[i]) r = j; } res.push_back(vector<int>{l, r}); } return res; } };
- 时间复杂度: , 两重循环。
- 空间复杂度: 定义了长度为的答案数组。
2. 单调栈
题目既然叫单调栈,那肯定是考察单调栈,这个词乍一看陌生又熟悉,我们先来学习下它是一种怎样的数据结构。
顾名思义,单调栈就是栈内元素有单调性的栈。对比普通栈,单调栈的入栈略有不同:单调栈在入栈的时候,需要先判断栈顶的元素,在加入后是否会破坏单调性,如果不会,直接入栈,否则一直弹出直到满足单调性。
对于本题来说,数组的下标是单调的,我们维护一个存储下标单调栈,然后遍历数组,做如下动作:
- 设当前遍历到
a[i]
,我们比较a[i]
和栈顶下标top对应的元素a[top]
- 如果
a[top] >= a[i]
: 说明top不会是第i个数的解,更不会是任何i以后的数的解(因为i又比top距离后面的数近,又比top小),就一直把top弹出来直到栈空或者栈顶元素小于a[i]. - 如果
a[top] < a[i]
: 说明第i个数的解就是top,因为我们确保了栈里面的数和下标都是单调递增的,且top之前的数肯定比a[top]远,又小,比top优秀的解也会在步骤2中加入栈,所以top就是答案。 - 因为i可能对是i右侧的候选解,把i加入栈。
这样得到了每一个i左边离i最近且小于arr[i]的位置,然后清空栈,再从右往左遍历,就得到了第二问的答案。
以样例中求l[i]为例,如图演示算法:
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums intvector * @return intvector<vector<>> */ vector<vector<int> > foundMonotoneStack(vector<int>& nums) { int n = nums.size(); vector<vector<int> > res; stack<int> s; vector<int> l(n), r(n); for (int i = 0; i < n; i++) { // 如果栈顶元素比nums[i]大,说明肯定需要被淘汰 // i左边的解都找到了,如上面分析i和i右侧的都不可能是这些更大的元素 // 所以一直弹栈,淘汰掉那些不可能成为答案的数字 while (!s.empty() && nums[s.top()] >= nums[i]) { s.pop(); } // 如果弹空了,说明i左边谁都比nums[i]大,就是无解,否则栈顶就是答案 l[i] = s.empty() ? -1 : s.top(); // i入栈,因为i有可能成为i右边的答案 s.push(i); } // 清空 while (!s.empty()) s.pop(); // 倒过来再算一遍,同样逻辑 for (int i = n-1; i >= 0; i--) { while (!s.empty() && nums[s.top()] >= nums[i]) { s.pop(); } r[i] = s.empty() ? -1 : s.top(); s.push(i); } for (int i = 0; i < n; i++) { res.push_back(vector<int>{l[i], r[i]}); } return res; } };
- 时间复杂度: , 只遍历了一轮数组,且每个数只能入栈、出栈1次,内层循环视为常数2,所以为。
- 空间复杂度:, 定义了大小为n的栈。