《剑指 Offer》专项突破版 - 面试题 38、39 和 40 : 通过三道面试题详解单调栈(C++ 实现)

news2024/11/25 13:55:06

目录

面试题 38 : 每日温度

面试题 39 : 直方图最大矩形面积

方法一、暴力求解

方法二、递归求解

方法三、单调栈法

面试题 40 : 矩阵中的最大矩形


 


面试题 38 : 每日温度

题目

输入一个数组,它的每个数字是某天的温度。请计算每天需要等几天才会出现更高的温度。例如,如果输入数组 [35, 31, 33, 36, 34],那么输出为 [3, 1, 1, 0, 0]。由于第 1 天的温度是 35℃,要等 3 天才会出现更高的温度 36℃,因此对应的输出为 3。第 4 天的温度是 36℃,后面没有更高的温度,它对应的输出是 0。其他的以此类推。

分析

解决这个问题的直观方法很多人很快就能想到。对于数组中的每个温度,可以扫描它后面的温度直到发现一个更高的温度为止。如果数组包含 n 天的温度,那么这种思路的时间复杂度是 O(n^2)。

下面通过一个具体的例子来分析这个问题的规律。假设输入的表示每天的温度的数组为 [35, 31, 33, 36, 34]。第 1 天的温度是 35℃,此时还不知道后面会不会有更高的温度,所以先将它保存到一个数据容器中。第 2 天的温度是 31℃,比第 1 天温度低,同样也保存到数据容器中。第 3 天的温度是 33℃,比第 2 天的温度高,由此可知,第 2 天需要等 1 天才有更高的温度。

每次从数组中读取某一天的温度,并且都将其与之前的温度(也就是已经保存在数据容器中的温度)相比较。从离它较近的温度开始比较看起来是一个不错的选择,也就是后存入数据容器中的温度先拿来比较,这契合 "后进先出" 的特性,所以可以考虑用栈来实现这个数据容器。同时,需要计算出现更高温度的等待天数,存入栈中的数据应该是温度在数组中的下标。等待的天数就是两个温度在数组中的下标之差

因此,处理到第 3 天的温度时,栈的状态为 [0, 1]。在知道第 2 天需要等 1 天将出现更高的温度之后,它就没有必要再保存在栈中,将它出栈。第 3 天的温度也需要入栈,以便和以后的温度比较,此时栈的状态为 [0, 2]。

第 4 天的温度是 36℃。从栈顶开始与之前的温度比较,它比第 3 天的温度 33℃ 高,因此第 3 天需要等 1 天就会出现更高的温度,这一天在数组中的下标 2 出栈。它也比第 1 天的温度 35℃ 高,因此第 1 天需要等 3 天才会出现更高的温度,这一天在数组中的下标 0 出现。然后将第 4 天在数组中的下标 3 入栈,以便和以后的温度比较。此时栈的状态为 [3]。最后一天的温度是 34℃,比位于栈顶的第 4 天的温度低,将其入栈,最终栈的状态是 [3, 4]。最终留在栈中的两天的后面都没有出现更高的温度。

解决这个问题的思路总结起来就是用一个栈保存每天的温度在数组中的下标。每次从数组中读取一个温度,然后将其与栈中保存的温度(根据下标可以得到温度)进行比较。如果当前温度比位于栈顶的温度高,那么就能知道位于栈顶那一天需要等待几天才会出现更高的温度。然后出栈 1 次,将当前温度与下一个位于栈顶的温度进行比较。如果栈中已经没有比当前温度低的温度,则将当前温度在数组中的下标入栈

代码实现

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> result(n, 0);
        stack<int> st;
        for (int i = 0; i < n; ++i)
        {
            while (!st.empty() && temperatures[i] > temperatures[st.top()])
            {
                result[st.top()] = i - st.top();
                st.pop();
            }
​
            st.push(i);
        }
        return result;
    }
};

保存在栈中的温度(通过数组下标可以得到温度)是递减排序的。这是因为如果当前温度比位于栈顶的温度高,位于栈顶的温度将出栈,所以每次入栈时当前温度一定比位于栈顶的温度低或相同

假设输入数组的长度为 n。虽然上述代码中有一个嵌套的二重循环,但它的时间复杂度是 O(n),这是因为数组中每个温度入栈、出栈各 1 次。这种解法的空间复杂度也是 O(n)。


面试题 39 : 直方图最大矩形面积

题目

直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组,数组中的数字是直方图中柱子的高。求直方图中最大矩形面积。假设直方图中柱子的宽都为 1。例如,输入数组 [3, 2, 5, 4, 6, 1, 4, 2],其对应的直方图如下图所示,该直方图中最大矩形面积为 12,如阴影部分所示。

分析

矩形的面积等于宽乘以高,因此只要能确定每个矩形的宽和高,就能计算它的面积。如果直方图中一个矩形从下标为 i 的柱子开始,到下标为 j 的柱子结束,那么这两根柱子之间的矩形(含两端的柱子)的宽是 j - i + 1。矩形的高就是两根柱子之间的所有柱子最矮的高度。例如,上图中从下标为 2 的柱子到下标为 4 的柱子之间的矩形宽度是 3,矩形的高度最多只能是 4,即它们之间 3 根柱子最矮的高度。

方法一、暴力求解

如果能逐一找出直方图中所有的矩形并比较它们的面积,就能得到最大矩形面积。下面使用嵌套的二重循环遍历所有矩形,并比较它们的面积。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int maxArea = 0;
        for (int i = 0; i < heights.size(); ++i)
        {
            int minHeight = heights[i];
            for (int j = i; j < heights.size(); ++j)
            {
                if (heights[j] < minHeight)
                    minHeight = heights[j];
                
                int area = minHeight * (j - i + 1);
​
                if (area > maxArea)
                    maxArea = area;
            }
        }
        return maxArea;
    }
};

这种解法的时间复杂度是 O(n^2),空间复杂度是 O(1)。

方法二、递归求解

上图的直方图中最矮的柱子在数组中的下标是 5,它的高度是 1。这个直方图的最大矩形有以下 3 种可能:

  1. 第 1 种是矩形通过这根最矮的柱子。通过最矮的柱子的最大矩形的高度是 1,宽度是 7

  2. 第 2 种是矩形的起始柱子和终止柱子都在最矮的柱子的左侧,也就是从下标为 0 的柱子到下标为 4 的柱子的直方图的最大矩形

  3. 第 3 种是矩形的起始柱子和终止柱子都在最矮的柱子的右侧,也就是从下标为 6 的柱子到下标为 7 的柱子的直方图的最大矩形

第 2 种和第 3 种本质上来说和求整个直方图的最大矩形面积是同一个问题,可以调用递归函数解决

class Solution {
private:
    int _largestRectangleArea(vector<int>& heights, int left, int right)
    {
        if (left > right)
            return 0;
        
        if (left == right)
            return heights[left];
​
        int minHeightIndex = left;
        for (int i = left + 1; i <= right; ++i)
        {
            if (heights[i] < heights[minHeightIndex])
                minHeightIndex = i;
        }
        int maxArea = heights[minHeightIndex] * (right - left + 1);
        int area1 = _largestRectangleArea(heights, left, minHeightIndex - 1);
        int area2 = _largestRectangleArea(heights, minHeightIndex + 1, right);
        if (area1 > maxArea)
            maxArea = area1;
        
        if (area2 > maxArea)
            maxArea = area2;
        
        return maxArea;
    }
​
public:
    int largestRectangleArea(vector<int>& heights) {
        return _largestRectangleArea(heights, 0, heights.size() - 1);
    }
};

假设输入数组的长度为 n。如果每次都能将 n 根柱子分成两根柱子数量为 n / 2 的子直方图,那么递归调用的深度为 O(logn),整个递归算法的时间复杂度是 O(nlogn)。但如果直方图中柱子的高度是排序的(递增排序或递减排序),那么每次最矮的柱子都位于直方图的一侧,递归调用的深度就是 O(n),此时递归算法的时间复杂度也变成 O(n^2)

递归算法的空间复杂度取决于调用栈的深度,因此平均空间复杂度是 O(logn),最坏情况下的空间复杂度是 O(n)

方法三、单调栈法

计算以每根柱子为顶的最大矩形面积,比较这些矩形面积就能得到直方图中的最大矩形面积

以某根柱子为顶的最大矩形,一定是从该柱子向两侧延伸直到遇到比它矮的柱子,这个最大矩形的高就是该柱子的高,最大矩形的宽是两侧比它矮的柱子中间的间隔。例如,为了求上图所示的直方图中以下标为 3 的柱子为顶的最大矩形面积,应该从该柱子开始向两侧延伸,左侧比它矮的柱子的下标是 1,右侧比它矮的柱子的下标是 5。因此,以下标为 3 的柱子为顶的最大矩形的高为 4,宽为 3(左右两侧比它矮的柱子的下标之差再减 1,即 5 - 1 - 1)。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n, -1);
        vector<int> right(n, n);
​
        stack<int> st;
        for (int i = n - 1; i >= 0; --i)
        {
            while (!st.empty() && heights[i] < heights[st.top()])
            {
                left[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
        st = stack<int>();
        for (int i = 0; i < n; ++i)
        {
            while (!st.empty() && heights[i] < heights[st.top()])
            {
                right[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
​
        int maxArea = 0;
        for (int i = 0; i < n; ++i)
        {
            int area = heights[i] * (right[i] - left[i] - 1);
            if (area > maxArea)
                maxArea = area;
        }
        return maxArea;
    } 
};

这种解法的时间复杂度是 O(n),空间复杂度也是 O(n)

 


面试题 40 : 矩阵中的最大矩形

题目

请在一个由 0、1 组成的矩阵中找出最大的只包含 1 的矩形并输出它的面积。例如,在下图的矩阵中,最大的只包含 1 的矩形如阴影部分所示,它的面积是 6。

分析

面试题 2.4 是关于最大矩形的,这个题目还是关于最大矩形的,它们之间有没有某种联系?如果能从矩阵中找出直方图,那么就能通过计算直方图中的最大矩形面积来计算矩阵中的最大矩形面积

直方图是由排列在同一基线上的相邻柱子组成的图形。由于题目要求矩形中只包含数字 1,因此将矩阵中上下相邻的值为 1 的格子看成直方图中的柱子。如果分别以上图中的矩阵的每行为基线,就可以得到 4 个由数字 1 的格子组成的直方图,如下图所示。

在将矩阵转换成多个直方图之后,就可以计算并比较每个直方图的最大矩形面积,所有直方图中的最大矩形就是整个矩阵中的最大矩形

代码实现

class Solution {
private:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n, -1);
        vector<int> right(n, n);
​
        stack<int> st;
        for (int i = n - 1; i >= 0; --i)
        {
            while (!st.empty() && heights[i] < heights[st.top()])
            {
                left[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
        st = stack<int>();
        for (int i = 0; i < n; ++i)
        {
            while (!st.empty() && heights[i] < heights[st.top()])
            {
                right[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
​
        int maxArea = 0;
        for (int i = 0; i < n; ++i)
        {
            int area = heights[i] * (right[i] - left[i] - 1);
            if (area > maxArea)
                maxArea = area;
        }
        return maxArea;
    }
    
public:
    int maximalRectangle(vector<string>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0)
            return 0;
​
        int result = 0;
        vector<int> heights(matrix[0].size(), 0);
        for (int i = 0; i < matrix.size(); ++i)
        {
            for (int j = 0; j < matrix[i].size(); ++j)
            {
                if (matrix[i][j] == '0')
                    heights[j] = 0;
                else
                    ++heights[j];
            }
​
            int maxArea = largestRectangleArea(heights);
            if (maxArea > result)
                result = maxArea;
        }
        return result;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1444664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轴角与旋转矩阵的转换

一、轴角转换成旋转矩阵 C实现 #include <iostream> #include <Eigen/Dense> #define _USE_MATH_DEFINES #include <math.h> using namespace std;int main() {double theta M_PI/2;//90度Eigen::Vector3d xyz(1, 0, 0);//x轴Eigen::AngleAxisd rotation…

【Django】Django日志管理

Django日志管理 Django使用Python内置的logging模块处理系统日志。 1.日志框架的组成元素 Python logging 配置由下面四部分组成&#xff1a; Loggers Handlers 过滤器 Formatters 1.1 Loggers logger是日志系统的入口&#xff0c;每个 logger都是命名了的 bucket&…

学生学习知识点总结作文试题练习题考试资讯网站源码

(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买) 资源简介 学生学习知识点总结作文试题练习题考试资讯网站源码+WAP手机版+采集优化版-整站打包 整站打包源码,简洁大…

使用Pillow来生成简单的红包封面

Pillow库&#xff08;Python Imaging Library的后继&#xff09;是一个强大而灵活的图像处理库&#xff0c;适用于Python。Pillow 库&#xff08;有时也称 PIL 库&#xff09; 是 Python 图像处理的基础库&#xff0c;它是一个免费开源的第三方库&#xff0c;由一群 Python 社区…

《统计学简易速速上手小册》第6章:多变量数据分析(2024 最新版)

文章目录 6.1 主成分分析&#xff08;PCA&#xff09;6.1.1 基础知识6.1.2 主要案例&#xff1a;客户细分6.1.3 拓展案例 1&#xff1a;面部识别6.1.4 拓展案例 2&#xff1a;基因数据分析 6.2 聚类分析6.2.1 基础知识6.2.2 主要案例&#xff1a;市场细分6.2.3 拓展案例 1&…

spring 入门 一

文章目录 Spring简介Spring的优势Spring的体系结构 Spring快速入门Spring程序开发步骤导入Spring开发的基本包坐标编写Dao接口和实现创建Spring核心配置文件在Spring配置文件中配置UserDaoImpl使用Spring的API获得Bean实例 Spring配置文件Bean标签基本配置Bean标签范围配置Bean…

Spring AI - 使用向量数据库实现检索式AI对话

Spring AI - 使用向量数据库实现检索式AI对话 Spring AI 并不仅限于针对大语言模型对话API进行了统一封装&#xff0c;它还可以通过简单的方式实现LangChain的一些功能。本篇将带领读者实现一个简单的检索式AI对话接口。 一、需求背景 在一些场景下&#xff0c;我们想让AI根据…

97.网游逆向分析与插件开发-网络通信封包解析-项目需求与需求拆解

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;窗口化助手与游戏窗口同步移动 项目需求&#xff1a; 为游戏的聊天功能做一个增强&#xff0c;能够使用户脱离游戏界面的情况下使用窗口化助手进行聊天&#xff0c;能够设置自动回复&#xff0c;记录…

PE 特征码定位修改程序清单 uiAccess

requestedExecutionLevel level"asInvoker" uiAccess"false" 可以修改这一行来启用禁用原程序的盾牌图标&#xff0c;似乎作用不大。以前没事写的一个小玩意&#xff0c;记录一下。 等同于这里的设置&#xff1a; 截图 代码如下&#xff1a; #include …

c语言游戏实战(4):人生重开模拟器

前言&#xff1a; 人生重开模拟器是前段时间非常火的一个小游戏&#xff0c;接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏&#xff1a; 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 &#xff08;1&#xff09; 游戏开始的时…

力扣[面试题 01.02. 判定是否互为字符重排(哈希表,位图)

Problem: 面试题 01.02. 判定是否互为字符重排 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1&#xff1a;哈希表 1.若两个字符串长度不相等&#xff0c;则一定不符合题意&#xff1b; 2.创建一个map集合&#xff0c;先将字符串s1中的每一个字符与其对应的数量存入集合…

【LeetCode每日一题】二维前缀和基本概念与案例

二维前缀和 根据某个块块 的 左上角坐标&#xff0c;和右下角坐标 求出 块块的累加和。 304. 二维区域和检索 - 矩阵不可变 /*** param {number[][]} matrix*/ var NumMatrix function(matrix) {let row matrix.length;let col matrix[0].length;// 初始化一个二维数组&am…

网络层DoS

网络层是OSI参考模型中的第三层&#xff0c;介于传输层和数据链路层之间&#xff0c;其目的 是实现两个终端系统之间数据的透明传送&#xff0c;具体功能包括&#xff1a;寻址和路由选择、连 接的建立、保持和终止等。位于网络层的协议包括ARP 、IP和ICMP等。下面就 ICMP为例&…

linux学习之虚拟地址

在以往的学习中我们经常接触地址&#xff0c;电脑像一个小房间&#xff0c;它的空间是有限不可重叠的&#xff0c;但是可以覆盖。想象一下如果我们要放很多东西进去&#xff0c;如果没有合理的安排&#xff0c;所有东西乱放&#xff0c;那么我们需要寻找某一个东西的时候需要把…

【开源】基于JAVA+Vue+SpringBoot的公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

stack和queue基本使用

stack和queue基本使用 stackqueuepriority_queuestack和queue容器底层的默认容器&#xff1a;deque stack stack是一种容器适配器&#xff08;容器适配器可以将一种接口转为用户需要的另一种接口&#xff0c;如将vector、list的接口封装转成用户需要的stack的接口&#xff09;…

Java实现音乐平台 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首音乐4.2 新增音乐4.3 新增音乐订单4.4 查询音乐订单4.5 新增音乐收藏 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的音乐平台&#xff0c;包含了音乐…

【Linux系统学习】6.Linux系统软件安装

实战章节&#xff1a;在Linux上部署各类软件 前言 为什么学习各类软件在Linux上的部署 在前面&#xff0c;我们学习了许多的Linux命令和高级技巧&#xff0c;这些知识点比较零散&#xff0c;进行练习虽然可以基础掌握这些命令和技巧的使用&#xff0c;但是并没有一些具体的实…

DP读书:《openEuler操作系统》(九)从IPC到网卡到卡驱动程序

DP读书&#xff1a;《openEuler操作系统》从IPC到网卡到卡驱动程序&#xff09; 上章回顾_SPI上节回顾_TCP 网卡驱动程序简介1.设备驱动2.总线与设备3.网卡及其抽象 驱动程序的注册与注销1. 注册2. 注销 设备初始化1. 硬件初始化2. 软件初始化 设备的打开与关闭1. 设备的打开2.…

lv14 中断处理原理:接口及按键驱动 14

一、什么是中断 一种硬件上的通知机制&#xff0c;用来通知CPU发生了某种需要立即处理的事件 分为&#xff1a; 内部中断 CPU执行程序的过程中&#xff0c;发生的一些硬件出错、运算出错事件&#xff08;如分母为0、溢出等等&#xff09;&#xff0c;不可屏蔽 外部中断 外设…