大家好,我是编程熊,双非逆袭选手,字节跳动、旷视科技前员工,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. Jittor框架API

    Jittor框架API 这里是Jittor主模块的API文档,可以通过import jittor来获取该模块. classjittor.ExitHooks exc_handler(exc_type,  ...

  2. 图分析Rapids cuGraph

    图分析Rapids cuGraph 英伟达(Nvidia)建立的新的开源库可能是推进分析和使图形数据库更快的秘密要素. 在Nvidia GPU上进行并行处理. Nvidia很久以前就不再只是" ...

  3. 特斯拉Tesla Model 3整体架构解析(下)

    特斯拉Tesla Model 3整体架构解析(中) Tesla Computer Unit 特斯拉已经开发了一个由自动驾驶仪和信息计算机组成的定制"液冷双计算平台"."他 ...

  4. 如何使用Nsight System?

    如何使用Nsight System?

  5. 9, java数据结构和算法: 直接插入排序, 希尔排序, 简单选择排序, 堆排序, 冒泡排序,快速排序, 归并排序, 基数排序的分析和代码实现

    内部排序: 就是使用内存空间来排序 外部排序: 就是数据量很大,需要借助外部存储(文件)来排序. 直接上代码: package com.lvcai; public class Sort { publi ...

  6. pytest初始化与清除(一)

    一.初始化函数 1.测试用例级别:def setup() 2.套件级别(在模块文件中定义):def setup_module() 3.套件级别(在类中定义): @classmethod def set ...

  7. 【NX二次开发】隐藏、显示对象UF_OBJ_set_blank_status

    隐藏.显示对象用UF_OBJ_set_blank_status() 查看对象显示情况用UF_OBJ_ask_display_properties() 效果: 源码: #include "Te ...

  8. 【模板】 RMQ求区间最值

    RMQ RMQ简单来说就是求区间的最大值(最小值) 核心算法:动态规划 RMQ(以下以求最大值为例) F[i,j]表示 从 i 开始 到i+2j -1这个区间中的最大值 状态转移方程 F[i,j]=m ...

  9. Docker启动PostgreSQL时创建多个数据库

    1 前言 在文章<Docker启动PostgreSQL并推荐几款连接工具>中我们介绍如何通过Docker来启动PostgreSQL,但只有一个数据库,如果想要创建多个数据库在同一个Dock ...

  10. Java操作SQL数据库(JDBC)

    0.引入驱动jar包 使用jdbc进行具体操作前,需要引入相关数据库的jar包, 或者使用mave管理依赖 <!-- https://mvnrepository.com/artifact/mys ...