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

目录

面试题 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.mfbz.cn/a/382059.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

290. Word Pattern(单词规律)

题目描述 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 提示: 1 < pattern.length < 300 pa…

python+django高校活动报名场地管理系统l1ro4

校园活动管理平台程序的开发&#xff0c;在数据库的选择上面&#xff0c;选择功能强大的MySQL数据库进行数据的存放操作。 技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5…

tkinter-TinUI-xml实战(10)展示画廊

tkinter-TinUI-xml实战&#xff08;10&#xff09;展示画廊 引言声明文件结构核心代码主界面统一展示控件控件展示界面单一展示已有展示多类展示 最终效果在这里插入图片描述 ![](https://img-blog.csdnimg.cn/direct/286fcaa2fa5648a992a0ac79b4efad82.png) ………… 结语 引言…

大数据Flume--入门

文章目录 FlumeFlume 定义Flume 基础架构AgentSourceSinkChannelEvent Flume 安装部署安装地址安装部署 Flume 入门案例监控端口数据官方案例实时监控单个追加文件实时监控目录下多个新文件实时监控目录下的多个追加文件 Flume Flume 定义 Flume 是 Cloudera 提供的一个高可用…

《CSS 简易速速上手小册》第4章:视觉美学(2024 最新版)

文章目录 4.1 颜色理论在 CSS 设计中的应用&#xff1a;网页的调色盘4.1.1 基础知识4.1.2 重点案例&#xff1a;创建一个具有情感设计的登录页面4.1.3 拓展案例 1&#xff1a;使用颜色增强信息的可视化表示4.1.4 拓展案例 2&#xff1a;利用颜色创建网站的品牌身份 4.2 字体与文…

C#使用哈希表对XML文件进行查询

目录 一、使用的方法 1.Hashtable哈希表 2.Hashtable哈希表的Add方法 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;示例 3.XML文件的使用 二、实例 1.源码 2.生成效果 可以通过使用哈希表可以对XML文件进行查询。 一、使用的方法 1.Hashtable哈希表…

视觉开发板—K210自学笔记(三)

本期我们来遵循其他单片机的学习路线开始去做一位点灯大师—点亮一个LED。那么第一步还是先知道K210里面的硬件电路是怎么连接的&#xff0c;需要查看上一节的文档&#xff0c;看看开发板原理图到底是哪个LED跟哪个IO连在一起。 一、硬件电路 根据之前官方提供的assembly draw…

最简单的基于 FFmpeg 的音频编码器(PCM 编码为 AAC)

最简单的基于 FFmpeg 的音频编码器&#xff08;PCM 编码为 AAC&#xff09; 最简单的基于 FFmpeg 的音频编码器&#xff08;PCM 编码为 AAC&#xff09;正文结果工程文件下载其他参考链接 最简单的基于 FFmpeg 的音频编码器&#xff08;PCM 编码为 AAC&#xff09; 参考雷霄骅…

【小沐学GIS】基于Android绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第二期3【小沐学GIS】…

Java:常用API接上篇 --黑马笔记

一、 StringBuilder类 StringBuilder代表可变字符串对象&#xff0c;相当于是一个容器&#xff0c;它里面的字符串是可以改变的&#xff0c;就是用来操作字符串的。 好处&#xff1a;StringBuilder比String更合适做字符串的修改操作&#xff0c;效率更高&#xff0c;代码也更…

例36:打开文件读出文件内容

1.建立一个EXE工程&#xff0c;在主窗体上放一个按钮&#xff0c;如图32。 图32 在按钮的单击事件中输入代码&#xff1a; Sub Form1_Command1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)Dim s as StringDim 文件 As CWSTR FF_OpenFileDialog(hWndForm,_"打开…

微信自动预约小程序开发指南:从小白到专家

在数字化时代&#xff0c;预约小程序已成为各类服务行业的必备工具。本文将指导你从零开始&#xff0c;通过第三方小程序制作平台&#xff0c;顺利开发出一款具有预约功能的实用小程序。 第一步&#xff1a;注册登录第三方小程序制作平台 首先&#xff0c;你需要选择一个适合你…

案例:三台主机实现 级联复制

介绍&#xff1a;级联复制架构 级联复制架构 是一种特殊的主从结构&#xff0c;之前聊到的几种主从结构都只有两层&#xff0c;但级联复制架构中会有三层&#xff0c;关系如下&#xff1a; 也就是在级联复制架构中&#xff0c;存在两层从库&#xff0c;这实际上属于一主多从架…

Hive-架构与设计

架构与设计 一、背景和起源二、框架概述1.设计特点 三、架构图1.UI交互层2.Driver驱动层3.Compiler4.Metastore5.Execution Engine 四、执行流程1.发起请求2.获取执行计划3.获取元数据4.返回元数据5.返回执行计划6.运行执行计划7.运行结果获取 五、数据模型1.DataBase数据库2.T…

fast.ai 机器学习笔记(四)

机器学习 1&#xff1a;第 11 课 原文&#xff1a;medium.com/hiromi_suenaga/machine-learning-1-lesson-11-7564c3c18bbb 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;这些笔记将继续…

[office] Excel2019函数MAXIFS怎么使用?Excel2019函数MAXIFS使用教程 #知识分享#微信#经验分享

Excel2019函数MAXIFS怎么使用&#xff1f;Excel2019函数MAXIFS使用教程 Excel2019函数MAXIFS怎么使用&#xff1f;这篇文章主要介绍了Excel2019函数MAXIFS使用教程,需要的朋友可以参考下 在今年&#xff0c;Excel除了新版本Excel2019&#xff0c;其中有一个新功能MAXIFS函数&am…

python_django高校运动会成绩管理系统4o4c3

田径运动会报名管理系统就是给学生进行网上报名&#xff0c;管理员管理报名信息的一种通用管理平台&#xff0c;从而方便管理人员对运动会的日常报名工作的管理。本系统的前台功能模块包括系统的基本操作、最新公告、运动项目和报名项目&#xff1b;系统的后台功能模块包括系统…

opencv计算机视觉

树莓派主机的无键盘解决 进入控制面板&#xff0c;更改适配器设置&#xff0c;WIFI属性&#xff0c;勾选 1.将网线两头分别接入树莓派和笔记本的网线接口 2.在无线连接属性那里勾选允许其他用户连接 3.运行cmd使用arp -a查看树莓派ip地址&#xff0c;或者使用ipscanner查看 cmd…

Nginx实战:1-安装搭建

目录 前言 一、yum安装 二、编译安装 1.下载安装包 2.解压 3.生成makefile文件 4.编译 5.安装执行 6.执行命令软连接 7.Nginx命令 前言 nginx的安装有两种方式&#xff1a; 1、yum安装&#xff1a;安装快速&#xff0c;但是无法在安装的时候带上想要的第三方包 2、…

巧用liteflow,告别if else,SpringBoot整合liteflow

假设有一个三个原子业务&#xff0c;吃饭、喝水、刷牙。 现在有三个场景&#xff0c;分别是 场景A: 吃饭->刷牙->喝水 官网地址&#xff1a;https://liteflow.cc/ 1.添加依赖&#xff1a; <dependency><groupId>com.yomahub</groupId><artifactI…