参考:
单调栈
| 类型 | 问题描述 | 完成 |
|---|---|---|
| 496. 下一个更大元素 I | ✅ | |
| 503. 下一个更大元素 II | ✅ | |
| 739. 每日温度 | ✅ | |
| 剑指 Offer II 038. 每日温度 | ✅ | |
| 402. 移掉 K 位数字 | ✅ | |
| 901. 股票价格跨度 | ✅ | |
| 155. 最小栈 | ||
| 918. 环形子数组的最大和 | ||
| 321. 拼接最大数 |
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 存放的是实际的值
Stack<Integer> stack = new Stack<Integer>();
Map<Integer, Integer> map = new HashMap<>();
int len2 = nums2.length;
for (int i = 0; i < len2; i++) {
map.put(nums2[i], i);
}
for (int j = len2 - 1; j >= 0; j--) {
// 后面的元素都小于自己,因为是寻找的右侧的下一个更大元素,有j在这边挡着,j后面比j小的就没有用了
while (!stack.isEmpty() && stack.peek() < nums2[j]) {
stack.pop();
}
if (stack.isEmpty()) {
// 把当前元素先放入栈里
stack.push(nums2[j]);
// 如果栈为空,说明对于j后面的元素不存在比它更大的元素
nums2[j] = -1;
} else {
// 如果栈不为空,那此时的栈顶就是j的下一个更大元素
int top = stack.peek();
// 把当前元素先放入栈里
stack.push(nums2[j]);
nums2[j] = top;
}
}
for (int i = 0; i < nums1.length; i++) {
int idx2 = map.getOrDefault(nums1[i], -1);
nums1[i] = idx2 == -1 ? -1 : nums2[idx2];
}
return nums1;
}
}
class Solution {
public int[] nextGreaterElements(int[] nums) {
Stack<Integer> stack = new Stack<Integer>();
int len = nums.length;
// 先把数组放到栈里
for (int i = len - 1; i >= 0; i--) {
while (!stack.isEmpty() && stack.peek() <= nums[i]) {
stack.pop();
}
stack.push(nums[i]);
}
for (int i = len - 1; i >= 0; i--) {
while (!stack.isEmpty() && stack.peek() <= nums[i]) {
stack.pop();
}
if (stack.isEmpty()) {
stack.push(nums[i]);
nums[i] = -1;
} else {
int top = stack.peek();
stack.push(nums[i]);
nums[i] = top;
}
}
return nums;
}
}
// 还有一种循环数组取模的方法
class Solution {
int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] res = new int[n];
Stack<Integer> s = new Stack<>();
// 数组长度加倍模拟环形数组
for (int i = 2 * n - 1; i >= 0; i--) {
// 索引 i 要求模,其他的和模板一样
while (!s.isEmpty() && s.peek() <= nums[i % n]) {
s.pop();
}
res[i % n] = s.isEmpty() ? -1 : s.peek();
s.push(nums[i % n]);
}
return res;
}
}
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
// 转化为求下一个更大元素的索引
Stack<Integer> stack = new Stack<Integer>();
int len = temperatures.length;
int[] ans = new int[len];
for(int i = len-1;i>=0;i--){
while(!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]){
stack.pop();
}
ans[i] = stack.isEmpty() ? 0: stack.peek() - i;
// 栈里存放元素的索引,方便计算天数
stack.push(i);
}
return ans;
}
}
class Solution {
public String removeKdigits(String num, int k) {
int len = num.length();
if(k == len){
return "0";
}
char[] chars = num.toCharArray();
Deque<Character> stack = new LinkedList<Character>();
int remain = len - k;
for(int i=0;i<len;i++){
while(!stack.isEmpty() && k > 0 && stack.peekLast() > chars[i]){
stack.pollLast();
k--;
}
stack.offerLast(chars[i]);
}
// 如果是升序,那么无法剔除任何元素,此时k还是一开始的k,需要从头到尾取remain个字符返回
while(k >0) {
k--;
stack.pollLast();
}
boolean isLeadZero = true;
StringBuilder ans = new StringBuilder();
while(!stack.isEmpty()){
char cur = stack.pollFirst();
if(isLeadZero && cur == '0'){
continue;
}
// 出现了其他元素,零不是开头了
isLeadZero = false;
ans.append(cur);
}
return ans.length() == 0? "0":ans.toString();
}
}
class StockSpanner {
Stack<StockInfo> stack;
public StockSpanner() {
stack = new Stack<>();
}
public int next(int price) {
// 默认只有自身
int ans = 1;
while(!stack.isEmpty() && stack.peek().price <= price){
StockInfo stockInfo = stack.pop();
ans += stockInfo.count;
}
// 把当前的价格放入栈中
stack.push(new StockInfo(price, ans));
return ans;
}
public class StockInfo{
int price;
int count;
StockInfo(int price, int count){
this.price = price;
this.count = count;
}
}
}
正常思路。
class MinStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MinStack() {
//维护两个栈,一个正常的栈,来保存元素的录入顺序,一个当前最小元素栈,来实现pop最小值
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int val) {
if (stack2.isEmpty() || val < stack2.peek()) {
stack2.push(val);
} else {
stack2.push(stack2.peek());
}
stack1.push(val);
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int getMin() {
return stack2.peek();
}
}
优化思路:不需要将两个栈的数量保持一致。
class MinStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MinStack() {
//维护两个栈,一个正常的栈,来保存元素的录入顺序,一个当前最小元素栈,来实现pop最小值
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int val) {
// 这边注意一定要有等于,因为可能输入多个相同的元素
if (stack2.isEmpty() || val <= stack2.peek()) {
stack2.push(val);
}
stack1.push(val);
}
public void pop() {
if (stack2.peek().equals(stack1.peek())) {
stack2.pop();
}
stack1.pop();
}
public int top() {
return stack1.peek();
}
public int getMin() {
return stack2.peek();
}
}
单调队列
既能够维护队列元素「先进先出」的时间顺序,又能够正确维护队列中所有元素的最值,这就是单调队列的作用。
| 类型 | 问题描述 | 完成 |
|---|---|---|
| 239. 滑动窗口最大值 | ✅ | |
| 1425. 带限制的子序列和 | ||
| 1696. 跳跃游戏 VI | ||
| 862. 和至少为 K 的最短子数组 | ||
| 918. 环形子数组的最大和 | ||
| 剑指 Offer 59 - I. 滑动窗口的最大值 | ✅ | |
| 剑指 Offer 59 - II. 队列的最大值 | ✅ |
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
int len = nums.length;
List<Integer> res = new ArrayList<>();
for (int i = 0; i < len; i++) {
// 先把窗口填满,需要留一个位置,因为size == k的时候就要计算第一次的最大值了
if (i < k - 1) {
window.push(nums[i]);
} else {
// 先加入队列
window.push(nums[i]);
// 记录最大值
res.add(window.max());
// 移除旧数字
window.poll(nums[i - k + 1]);
}
}
int[] arr = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
arr[i] = res.get(i);
}
return arr;
}
public class MonotonicQueue {
private LinkedList<Integer> maxq = new LinkedList<Integer>();
// 给队尾插入元素
public void push(int n) {
// 把比自己小的元素都挤出去,保持队列的单调性
while (!maxq.isEmpty() && maxq.getLast() < n) {
maxq.pollLast();
}
maxq.addLast(n);
}
//剔除队头元素,如果相等的话
public void poll(int n) {
if (n == maxq.getFirst()) {
maxq.pollFirst();
}
}
// 返回当前窗口中的最大值
public int max() {
return maxq.getFirst();
}
}
}
//leetcode submit region end(Prohibit modification and deletion)
class MaxQueue {
LinkedList<Integer> monotonousQueue = null;
LinkedList<Integer> elementQueue = null;
public MaxQueue() {
monotonousQueue = new LinkedList<>();
elementQueue = new LinkedList<>();
}
public int max_value() {
// 队头就是最大的元素
return monotonousQueue.isEmpty() ? -1 : monotonousQueue.getFirst();
}
public void push_back(int value) {
while (!monotonousQueue.isEmpty() && monotonousQueue.getLast() < value) {
monotonousQueue.pollLast();
}
monotonousQueue.addLast(value);
elementQueue.addLast(value);
}
public int pop_front() {
if (elementQueue.isEmpty()) {
return -1;
}
int frontVal = elementQueue.pollFirst();
if (frontVal == monotonousQueue.getFirst()) {
monotonousQueue.pollFirst();
}
return frontVal;
}
}