大家好,我是编程熊,双非逆袭选手,字节跳动、旷视科技前员工,ACM金牌,保研985,《ACM金牌选手讲解LeetCode算法系列》作者。

上一篇文章讲解了《线性表》中的数组、链表、栈和队列的概念和基本应用,本文讲解栈和队列的高级应用。

  • 单调栈
  • 双端队列
  • 滑动窗口

单调栈

介绍

单调栈 = 单调 + 栈,因此其同时满足两个特性: 单调性、栈的特点。

  • 单调性: 单调栈里面所存放的数据是有序的(单调递增或递减)。
  • 栈: 后进先出。

因其满足单调性和每个数字只会入栈一次,所以可以在时间复杂度 O(n) 的情况下解决一些问题。

下图是单调栈的图解,栈内数字满足单调性,且满足栈的后进先出的特性。

例题

LeetCode 739. 每日温度

题意

给定每天的温度,求对于每一天需要等几天才可以等到更暖和的一天。如果该天之后不存在 更暖和的天气,则记为 0。

输出一个一维数组,表示每天需要等待的天数。

示例
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
题解

建立单调(非增)栈,栈存放每天的温度,为了方便计算天数,栈中存储的每天的温度在数组中下标,可以通过下标得到对应天的温度。

设温度数组为 a,从左向右依次遍历数组 a,假设当前遍历到数组位置为 j,则对应的天温度为 a[j],设栈顶元素的位置为 i,则对应的天的温度a[i] ,分为两种情况讨论。

  • 如果 a[j] > a[i],执行以下三步。

    • 表明比第 i 天更暖和的一天为第 j 天,则第 i 天的答案为 j-i,那么可以将栈顶元素弹出。
    • 重复检查栈顶元素,直至栈顶元素的 a[j] <= a[i] 或者 栈为空。
    • j 入栈。
  • 如果 a[j] <= a[i]
    • 表明第 i 天没有找到更暖和的一天,无需对栈操作。
    • j 入栈。

然后继续遍历温度数组 a,考虑下一天,直至结束。

遍历结束,若栈不为空,则说明栈内的天找不到更暖和的一天,记为 0

代码
class Solution {
public int[] dailyTemperatures(int[] T) {
int[] ans = new int[T.length];
Deque<Integer> s = new LinkedList<Integer>();
for(int i = 0; i < T.length; i++) {
while(!s.isEmpty() && T[i] > T[s.peek()]) {
ans[s.peek()] = i - s.pop();
}
s.push(i);
}
return ans;
}
}

LeetCode 316. 去除重复字母

题意

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

示例
输入:s = "bcabc"
输出:"abc"
题解

首先思考这个问题的一个简单版本。给一个字符串删除一个字符,使得字典序最小。

  • 解法: 字典序就是字母的大小顺序,我们想字典序最小,那应删除满足 s[i] > s[i+1] 的最小位置 i 上的字符。

回到这个问题,我们也是想尽可能的删除满足 s[i] > s[i+1] 的最小位置 i 上的字符,如果每次都是遍历一遍字符串删除一个字符,这样时间复杂度可能退化到 O(n^2)

优化方法: 单调栈。

单调栈中存放的是字符,从左往右遍历字符串 s, 设当前遍历到字符串的位置 i,栈顶字符为c,考虑 s[i] 和 栈顶字符的大小关系、位置 i 的字符不在栈中,可分为两种。

  • c > s[i] 并且 位置 i 的字符不在栈中 并且 在位置 i 后面还存在字符 c,那么将 c 从栈中弹出。重复这个过程,直到 c > s[i] 不成立 或者 栈为空。
  • 不满足上述条件,直接将 s[i] 放入栈中。

继续遍历字符串 s,直至结束,最后栈中的字符就是题目要求的字典序最小的字符串。

代码
class Solution {
public String removeDuplicateLetters(String s) {
int[] count = new int[30];
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a']++;
}
boolean[] vis = new boolean[30];
StringBuffer ans = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i) - 'a';
if (!vis[c]) {
while ((ans.length() > 0) && (count[ans.charAt(ans.length() - 1) - 'a'] > 0)
&& ((ans.charAt(ans.length() - 1) - 'a') > c)) {
vis[ans.charAt(ans.length() - 1) - 'a'] = false;
ans.deleteCharAt(ans.length() - 1);
}
vis[c] = true;
ans.append(s.charAt(i));
}
count[c]--;
}
return ans.toString();
}
}

习题推荐

  1. LeetCode 496. 下一个更大元素 I
  2. LeetCode 1475. 商品折扣后的最终价格
  3. LeetCode 503. 下一个更大元素 II

双端队列 & 滑动窗口

介绍

双端队列是普通队列的加强版 ,区别于队列只能从队头出队,队尾入队;双端队列既可以在队头入队和出队,也可以在队尾入队和出队。

下图是双端队列的的图解,可以看出,双端队列既可以在队头入队和出队,也可以在队尾入队和出队。

例题

LeetCode 239. 滑动窗口最大值

题意

给你一个整数数组 nums,有一个大小为 k 的滑动窗口,从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口移动过程中每个窗口中的最大值。

示例
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
题解

滑动窗口经典题,维护一个单调的双端队列,为了方便,双端队列里面的数组的下标,从前往后遍历数组,需要实现两个功能。

  • 队头位置下标当前遍历位置下标 的距离大于 k,则删除队头元素,保证了队头下标在当前滑动窗口内
  • 队尾位置下标对应的值 小于 当前位置的值,则删除队尾元素,保证了队头下标对应的值是最大的

其次将当前遍历位置下标放入双端队列,然后遍历数组的下一个位置,直至结束。

代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> q = new LinkedList<>();
int ans[] = new int[nums.length - k + 1];
for (int i = 0; i < k; i++) {
while (!q.isEmpty() && nums[q.getLast()] < nums[i]) {
q.removeLast();
}
q.addLast(i);
}
ans[0] = nums[q.getFirst()];
for (int i = k; i < nums.length; i++) {
while(!q.isEmpty() && (i - q.getFirst() >= k)) {
q.removeFirst();
}
while(!q.isEmpty() && nums[q.getLast()] < nums[i]) {
q.removeLast();
}
q.addLast(i);
ans[i - k + 1] = nums[q.getFirst()];
}
return ans;
}
}

LeetCode 3. 无重复字符的最长子串

题意

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
题解

观察样例,我们可以发现,依次递增地枚举子串的起始位置,那么合法的结束为止一定是递增的,因为对于起始位置 i-1 ,假设其不含有重复字符的最远右位置 j;那么对于起始位置为 i 的子串,因为 [i-1,j] 不含有重复字符,其不含有重复字符的最远右位置一定大于等于 i,因此我们考虑使用滑动窗口来解决本题。

我们可以固滑动窗口的右边界,找到最远的不含有重复字符的左边界 ,根据上面我们观察得到的性质可以,不含有重复字符的左边界是非递减的。

代码具体实现上我们可以用 双端队列实现滑动窗口,辅助数组 cnt 统计窗口内每个字符出现的次数 ,来判断窗口是否有重复的字符。

代码
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] cnt = new char[128];
LinkedList<Character> q = new LinkedList<Character>();
int ans = 0;
for (int i = 0; i < s.length(); i++) {
q.add(s.charAt(i));
cnt[s.charAt(i)]++;
while (cnt[s.charAt(i)] > 1) {
char frontC = q.pollFirst();
cnt[frontC]--;
}
ans = Math.max(ans, q.size());
}
return ans;
}
}

习题推荐

LeetCode 209. 长度最小的子数组

【下面是粉丝福利】

【计算机学习核心资源】: 涵盖了所有计算机学习核心资源,多看看进大厂问题不大。

【github宝藏仓库】: 对学习和面试都非常有帮助,学完超过99%同龄人。

ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》的更多相关文章

  1. ACM金牌选手讲解LeetCode算法《哈希》

    大家好,我是编程熊. 往期文章介绍了<线性表>中的数组.链表.栈.队列,以及单调栈和滑动窗口. ACM金牌选手讲解LeetCode算法<线性表> ACM金牌选手讲解LeetCo ...

  2. ACM金牌选手算法讲解《线性表》

    哈喽,大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM亚洲区域赛金牌,保研985研究生,分享算法与数据结构.计算机学习经验,帮助大家进大厂~ 公众号:『编程熊』 文章首发于: ACM ...

  3. ACM金牌选手整理的【LeetCode刷题顺序】

    算法和数据结构知识点图 首先,了解算法和数据结构有哪些知识点,在后面的学习中有 大局观,对学习和刷题十分有帮助. 下面是我花了一天时间花的算法和数据结构的知识结构,大家可以看看. 后面是为大家 精心挑 ...

  4. 编程熊讲解LeetCode算法《二叉树》

    大家好,我是编程熊. 往期我们一起学习了<线性表>相关知识. 本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题. 如果还未学习 ...

  5. LeetCode算法题-Design HashMap(Java实现)

    这是悦乐书的第299次更新,第318篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第167题(顺位题号是706).在不使用任何内置哈希表库的情况下设计HashMap.具体 ...

  6. LeetCode算法题-Design HashSet(Java实现)

    这是悦乐书的第298次更新,第317篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第166题(顺位题号是705).不使用任何内建的hash表库设计一个hash集合,应包含 ...

  7. LeetCode算法题-Relative Ranks(Java实现)

    这是悦乐书的第248次更新,第261篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第115题(顺位题号是506).根据N名运动员的得分,找到他们的相对等级和得分最高的三个 ...

  8. LeetCode算法题-Linked List Cycle(Java实现)

    这是悦乐书的第176次更新,第178篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第35题(顺位题号是141).给定一个链表,确定它是否有一个循环. 本次解题使用的开发工 ...

  9. 小旭讲解 LeetCode 53. Maximum Subarray 动态规划 分治策略

    原题 Given an integer array nums, find the contiguous subarray (containing at least one number) which ...

随机推荐

  1. FPGA最全科普总结

    FPGA最全科普总结   FPGA 是可以先购买再设计的"万能"芯片.FPGA (Field Programmable Gate Array)现场可编程门阵列,是在硅片上预先设计实 ...

  2. GVS灵动系列家族上新 | 稳住,我们能“银”

    用天赐的色库 给生活增加些艺术的气息 生活本应多点探索的乐趣 今天 GVS灵动系列家族流光银(白玻璃) 全新上线 用灵感朝圣自然之道 邂逅另一种柔性美学 与早前的经典黑.星耀灰 和而不同,美美与共 携 ...

  3. 最新版robotframework4.0.3版本安装

    python最新版本已经到了3.9了.对应的最新版本也更新到robotframework4.0.3. robotframework支持的自动化测试的类型以及需要的库: 1.ui自动化测试:Seleni ...

  4. [Azure DevOps] 使用 Inno Setup 制作桌面软件安装包

    1. 桌面应用程序的 CI/CD 桌面应用程序的 CI/CD 过程和网站有一些不同,毕竟桌面应用程序的"部署"只是将安装包分发到目标位置,连应用商店都不用上,根据公司的管理流程可以 ...

  5. 源码级别理解 Redis 持久化机制

    文章首发于公众号"蘑菇睡不着",欢迎来访~ 前言 大家都知道 Redis 是一个内存数据库,数据都存储在内存中,这也是 Redis 非常快的原因之一.虽然速度提上来了,但是如果数据 ...

  6. 08:'my_tag' is not a registered tag library. Must be one of

    确保每次修改模板标签时都重新启动 Django 开发服务器(或确保它自己重新启动).如果服务器没有重新启动,Django 将不会注册标签. 从 django 1.9 开始,您可以在如下设置中加载这些新 ...

  7. 合宙Luat | Cat.1 Socket数据收不到?学会两招不掉线

    1 服务器收不到Socket数据的原因 Socket是大家使用Cat.1模块常用的功能之一,但Cat.1模块不是直接跟服务器连接,而是通过NAT(即网络地址转换)与服务器连接. 一个会话建立后会在NA ...

  8. 「模拟8.23」one递推,约瑟夫

    前置芝士约瑟夫问题 这样大概就是板子问题了 考场的树状数组+二分的60分暴力??? 1 #include<bits/stdc++.h> 2 #define int long long 3 ...

  9. 【题解】Luogu p2016 战略游戏 (最小点覆盖)

    题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题. 他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能 ...

  10. Java-Java8特性(更新中)

    Java8新特性 之前零零散散写了很多java8的内容,今天做一个整理,也算是整理用到的内容,当然细化的话还有很多,只是说暂时用不到,为了面试的话已经够了 日期计算 Lambda表达式 函数式接口(比 ...