①维护一个单调递减栈,一旦出现非递减的元素,就一直弹出栈中元素,直到依然满足递减栈
②栈中元素存放的是数组的索引,便于计算多少天后出现更高温度
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[temperatures.length];
for (int i = 0; i < temperatures.length; i++) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
res[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
return res;
}
}
①如果找不到更大元素返回-1,所以数组初始化为-1
②用map来存储nums1中元素和下标的关系
核心还是维护单调栈
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int[] result = new int[nums1.length];
int[] res = new int[nums2.length];
Map<Integer, Integer> map = new HashMap<>();
Arrays.fill(res, -1);
Stack<Integer> s = new Stack<>();
for (int i = 0; i < nums2.length; i++) {
map.put(nums2[i], i);
while (!s.isEmpty() && nums2[i] > nums2[s.peek()]) {
res[s.peek()] = nums2[i];
s.pop();
}
s.push(i);
}
for (int i = 0; i < nums1.length; i++) {
result[i] = res[map.get(nums1[i])];
}
return result;
}
}
循环地搜索下一个更大的数,所以循环的长度改为2*len,同时存进stack中的索引也是取余过的
class Solution {
public int[] nextGreaterElements(int[] nums) {
int len = nums.length;
int[] res = new int[len];
Arrays.fill(res, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < 2 * len; i++) {
while (!stack.isEmpty() && nums[i % len] > nums[stack.peek()]) {
res[stack.peek()] = nums[i % len];
stack.pop();
}
stack.push(i % len);
}
return res;
}
}
单调栈应用的经典题
①按列计算会比较清晰,每一列能够接的水就是这一列左边的最大值和右边最大值中较小的一个减去这一列的高度
②第一列和最后一列不装雨水
解法一:DP,定义maxLeft maxRight两个数组,分别表示每个位置的左边最大值和右边最大值
class Solution {
public int trap(int[] height) {
int len = height.length;
if (len <= 2) {
return 0;
}
int[] maxRight = new int[len];
int[] maxLeft = new int[len];
maxLeft[0] = height[0];
maxRight[len - 1] = height[len - 1];
for (int i = 1; i < len; i++) {
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
for (int i = len - 2; i >= 0; i--) {
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
int cnt = 0;
int count;
for (int i = 1; i < len - 1; i++) {
count = Math.min(maxLeft[i], maxRight[i]) - height[i];
if (count > 0) {
cnt += count;
}
}
return cnt;
}
}
解法二:单调栈,一旦出现不满足单调递减的元素,就记录栈顶元素,找到栈顶元素左边第一个大于栈顶元素的数,此时宽就是当前元素和左边第一个大于栈顶元素的数的差值减一,高度的话只需要计算栈顶元素和当前元素和左边第一个大于栈顶元素中较小的一个的差值。相当于是通过分段行的形式。
class Solution {
public int trap(int[] height) {
Stack<Integer> stack = new Stack<>();
int cnt = 0;
for (int i = 0; i < height.length; i++) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int cur = height[stack.pop()];
while (!stack.isEmpty() && height[stack.peek()] == cur) {
stack.pop();
}
if (!stack.isEmpty()) {
int h = Math.min(height[i], height[stack.peek()]) - cur;
int w = i - stack.peek() - 1;
cnt += h * w;
}
}
stack.push(i);
}
return cnt;
}
}
和接雨水类型,求的是栈顶元素左右两边出现的第一个较小的数,所以栈里的元素是需要满足单调递增的。
那么问题就变成求两个小元素中间矩形能围成的最大面积,因为是单调递增的,所以当计算完一个栈顶元素,后续是不会再用到该元素的,所以直接出栈即可。同时,因为计算的是中间的矩形的面积,所以每个矩形的宽是两个较小元素的下标之差减一。
解法一:单调栈
class Solution {
public int largestRectangleArea(int[] heights) {
int[] newHeights = new int[heights.length + 2];
for (int i = 0; i < heights.length; i++) {
newHeights[i + 1] = heights[i];
}
heights = newHeights;
Stack<Integer> stack = new Stack<>();
int max = 0;
for (int i = 0; i < heights.length; i++) {
while (!stack.isEmpty() && heights[i] < heights[stack.peek()]) {
int h = heights[stack.pop()];
int w = i - stack.peek() - 1;
max = Math.max(max, h * w);
}
stack.push(i);
}
return max;
}
}
解法二:DP
因为是找两边第一个小于当前元素的值,所以需要从用while直到找到目标值
public int largestRectangleArea2(int[] heights) {
int[] minLeft = new int[heights.length];
int[] minRight = new int[heights.length];
int max = 0;
minLeft[0] = -1;
for (int i = 1; i < heights.length; i++) {
int t = i - 1;
while (t >= 0 && heights[t] >= heights[i]) {
t = minLeft[t];
}
minLeft[i] = t;
}
minRight[heights.length - 1] = heights.length;
for (int i = heights.length - 2; i >= 0; i--) {
int t = i + 1;
while (t < heights.length && heights[t] >= heights[i]) {
t = minRight[t];
}
minRight[i] = t;
}
for (int i = 0; i < heights.length; i++) {
max = Math.max(max, heights[i] * (minRight[i] - minLeft[i] - 1));
}
return max;
}
维护一个单调递增栈,当出现不满足条件的元素时,则相当于找到了栈顶元素的右边第一个比其小的数,下标为i,取出栈顶元素,下标为cur,然后获取栈顶元素stack.peek(),此时栈顶元素则为cur的左边第一个比其小的数,所以cur此时覆盖的区域为(stack.peek(), i ),左区间的元素个数为cur - stack.peek(),右区间的元素个数为i - cur,所以可以组成的子集个数为
(cur - stack.peek())* (i - cur) ,再乘栈顶元素的值,就得到了这部分子集最小值的和
class Solution {
public int sumSubarrayMins(int[] arr) {
int[] newArr = new int[arr.length + 2];
for (int i = 0; i < arr.length; i++) {
newArr[i + 1] = arr[i];
}
arr = newArr;
Deque<Integer> stack = new LinkedList<>();
long sum = 0;
for (int i = 0; i < arr.length; i++) {
while (!stack.isEmpty() && arr[i] < arr[stack.peek()]) {
int min = stack.pop();
long cnt = (long)(i - min) * (long)(min - stack.peek()) * (long)arr[min];
sum += cnt;
}
stack.push(i);
}
return (int)(sum % 1000000007);
}
}
leetcode1856.子数组最小乘积的最大值
哨兵 + 前缀和 + 单调栈 和 907基本一样
class Solution {
public int maxSumMinProduct(int[] nums) {
int[] newNums = new int[nums.length + 2];
for (int i = 0; i < nums.length; i++) {
newNums[i + 1] = nums[i];
}
nums = newNums;
long[] sum = new long[nums.length];
for (int i = 1; i < nums.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
Deque<Integer> stack = new LinkedList<>();
long max = 0;
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[i] < nums[stack.peek()]) {
int cur = stack.pop();
long res = sum[i - 1] - sum[stack.peek()];
max = Math.max(max, res * nums[cur]);
}
stack.push(i);
}
return (int)(max % 1000000007);
}
}
哨兵 + 前缀和的前缀和 + 单调栈
如果前缀和的定义是 s[i] = a[0] + ... + a[i - 1], 那么 a[l] + .. + a[r] = s[r + 1] - s[l]
关键代码:
(cur - l + 1) * (sos[r + 2] - sos[cur + 1]) - (r - cur + 1) * (sos[cur + 1] - sos[l])
前缀和的前缀和数组和原数组的对应关系为 i + 2,所以sos[r + 2] 是以下标为r的元素为右端点,
(r - cur + 1) * (sos[cur + 1] - sos[l])这个式子中的sos[cur + 1]实际上就是下标为cur - 1的元素,其实就是区间[l , cur - 1] 和 区间 [cur + 1 , r] 子集的和,建议记住公式
class Solution {
public int totalStrength(int[] strength) {
long total = 0;
int mod = 1000000007;
int[] newStrength = new int[strength.length + 2];
for (int i = 0; i < strength.length; i++) {
newStrength[i + 1] = strength[i];
}
strength = newStrength;
long[] sos = new long[strength.length + 2];
long sum = 0;
for (int i = 1; i < strength.length; i++) {
sum += strength[i - 1];
sos[i + 1] = (sos[i] + sum) % mod;
}
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < strength.length; i++) {
while (!stack.isEmpty() && strength[i] < strength[stack.peek()]) {
int cur = stack.pop();
int l = stack.peek() + 1;
int r = i - 1;
long tol = ((cur - l + 1) * (sos[r + 2] - sos[cur + 1]) -
(r - cur + 1) * (sos[cur + 1] - sos[l])) % mod;
total = (total + tol * strength[cur]) % mod;
}
stack.push(i);
}
return (int)(total + mod)% mod;
}
}
单调栈,和接雨水有点像,按行统计,同时进行累加,把原数组初始化为每个位置的延申的高度,
每个点都只需要计算以自己为右下角且为当前位置能向上延申的长度为高可以形成的矩形即可
class Solution {
public int numSubmat(int[][] mat) {
int res = 0;
int m = mat.length;
int n = mat[0].length;
int[][] dp = new int[m][n];
for (int i = 1; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1) {
mat[i][j] = mat[i - 1][j] + 1;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int p = j;
while (--p >= 0 && mat[i][p] >= mat[i][j]){}
if (p != -1) {
dp[i][j] = mat[i][j] * (j - p) + dp[i][p];
} else {
dp[i][j] = mat[i][j] * (j + 1);
}
res += dp[i][j];
}
}
return res;
}
}