Leetcode452. 用最少数量的箭引爆气球

Every day a Leetcode

题目来源:452. 用最少数量的箭引爆气球

解法1:排序 + 贪心

题解:用最少数量的箭引爆气球

我们首先随机地射出一支箭,再看一看是否能够调整这支箭地射出位置,使得我们可以引爆更多数目的气球。

在这里插入图片描述

如图 1-1 所示,我们随机射出一支箭,引爆了除红色气球以外的所有气球。我们称所有引爆的气球为「原本引爆的气球」,其余的气球为「原本完好的气球」。可以发现,如果我们将这支箭的射出位置稍微往右移动一点,那么我们就有机会引爆红色气球,如图 1-2 所示。

那么我们最远可以将这支箭往右移动多远呢?我们唯一的要求就是:原本引爆的气球只要仍然被引爆就行了。这样一来,我们找出原本引爆的气球中右边界位置最靠左的那一个,将这支箭的射出位置移动到这个右边界位置,这也是最远可以往右移动到的位置:如图 1-3 所示,只要我们再往右移动一点点,这个气球就无法被引爆了。

为什么「原本引爆的气球仍然被引爆」是唯一的要求?别急,往下看就能看到其精妙所在。

因此,我们可以断定:

一定存在一种最优(射出的箭数最小)的方法,使得每一支箭的射出位置都恰好对应着某一个气球的右边界。

这是为什么?我们考虑任意一种最优的方法,对于其中的任意一支箭,我们都通过上面描述的方法,将这支箭的位置移动到它对应的「原本引爆的气球中最靠左的右边界位置」,那么这些原本引爆的气球仍然被引爆。这样一来,所有的气球仍然都会被引爆,并且每一支箭的射出位置都恰好位于某一个气球的右边界了。

有了这样一个有用的断定,我们就可以快速得到一种最优的方法了。考虑所有气球中右边界位置最靠左的那一个,那么一定有一支箭的射出位置就是它的右边界(否则就没有箭可以将其引爆了)。当我们确定了一支箭之后,我们就可以将这支箭引爆的所有气球移除,并从剩下未被引爆的气球中,再选择右边界位置最靠左的那一个,确定下一支箭,直到所有的气球都被引爆。

我们可以写出如下的伪代码:

let points := [[x(0), y(0)], [x(1), y(1)], ... [x(n-1), y(n-1)]],表示 n 个气球
let burst := [false] * n,表示每个气球是否被引爆
let ans := 1,表示射出的箭数

将 points 按照 y 值(右边界)进行升序排序

while burst 中还有 false 值 do
    let i := 最小的满足 burst[i] = false 的索引 i
    for j := i to n-1 do
        if x(j) <= y(i) then
            burst[j] := true
        end if
        ans := ans + 1
    end for
end while

return ans

这样的做法在最坏情况下时间复杂度是 O(n2),即这 n 个气球对应的区间互不重叠,while 循环需要执行 n 次。

代码:

/*
 * @lc app=leetcode.cn id=452 lang=cpp
 *
 * [452] 用最少数量的箭引爆气球
 */

// @lc code=start
class Solution
{
private:
    static bool cmp(const vector<int> A, const vector<int> B)
    {
        return A[1] < B[1];
    }
    bool remainBalloons(vector<bool> &burst)
    {
        for (int i = 0; i < burst.size(); i++)
            if (burst[i] == false)
                return true;
        return false;
    }

public:
    int findMinArrowShots(vector<vector<int>> &points)
    {
        if (points.empty())
            return 0;
        int n = points.size();
        vector<bool> burst(n, false);
        int ans = 0;
        sort(points.begin(), points.end(), cmp);
        while (remainBalloons(burst))
        {
            int i = 0;
            while (i < n && burst[i] == true)
                i++;
            for (int j = i; j < n; j++)
            {
                if (points[j][0] <= points[i][1])
                    burst[j] = true;
            }
            ans++;
        }
        return ans;
    }
};
// @lc code=end

结果:超时

在这里插入图片描述

复杂度分析:

时间复杂度:O(n2),其中 n 是数组 points 的长度。排序的时间复杂度为 O(nlogn),对所有气球进行遍历并计算答案的时间复杂度为 O(n),其在渐进意义下小于前者,因此可以忽略。

空间复杂度:O(n),其中 n 是数组 points 的长度。我们用到了长度为 n 的复制数组 burst。

解法2:

那么我们如何继续进行优化呢?

事实上,在内层的 j 循环中,当我们遇到第一个不满足 x(j)≤y(i) 的 j 值,就可以直接跳出循环,并且这个 y(j) 就是下一支箭的射出位置。为什么这样做是对的呢?我们考虑某一支箭的索引 it 以及它的下一支箭的索引 jt,对于索引在 jt 之后的任意一个可以被 it 引爆的气球,记索引为 j0 ,有:x(j0)≤y(it)。

由于 y(it)≤y(jt) 显然成立,那么 x(j0)≤y(jt) 也成立,也就是说:当前这支箭在索引 jt(第一个无法引爆的气球)之后所有可以引爆的气球,下一支箭也都可以引爆。因此我们就证明了其正确性,也就可以写出如下的伪代码:

let points := [[x(0), y(0)], [x(1), y(1)], ... [x(n-1), y(n-1)]],表示 n 个气球
let pos := y(0),表示当前箭的射出位置
let ans := 1,表示射出的箭数

将 points 按照 y 值(右边界)进行升序排序

for i := 1 to n-1 do
    if x(i) > pos then
        ans := ans + 1
        pos := y(i)
    end if
end for

return ans

这样就可以将计算答案的时间从 O(n2) 降低至 O(n)。

代码:

class Solution
{
private:
    static bool cmp(const vector<int> &A, const vector<int> &B)
    {
        return A[1] < B[1];
    }

public:
    int findMinArrowShots(vector<vector<int>> &points)
    {
        if (points.empty())
            return 0;
        // sort(points.begin(), points.end(), [](const vector<int> &u, const vector<int> &v)
        //      { return u[1] < v[1]; });
        sort(points.begin(), points.end(), cmp);
        int pos = points[0][1];
        int ans = 1;
        for (const vector<int> &balloon : points)
        {
            if (balloon[0] > pos)
            {
                pos = balloon[1];
                ans++;
            }
        }
        return ans;
    }
};

结果:

在这里插入图片描述

复杂度分析:

时间复杂度:O(nlogn),其中 n 是数组 points 的长度。排序的时间复杂度为 O(nlogn),对所有气球进行遍历并计算答案的时间复杂度为 O(n),其在渐进意义下小于前者,因此可以忽略。

空间复杂度:O(logn),其中 n 是数组 points 的长度。即为排序需要使用的栈空间。

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

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

相关文章

既然有了IP地址,为什么还需要MAC地址?两者到底有啥区别,深入分析后终于明白了!

在计算机网络中&#xff0c;IP地址和MAC地址是两个最基本的概念。IP地址在互联网中是用于标识主机的逻辑地址&#xff0c;而MAC地址则是用于标识网卡的物理地址。虽然它们都是用于标识一个设备的地址&#xff0c;但是它们的作用和使用场景是不同的。 IP地址是在网络层&#xff…

logstash同步数据从kafka到es集群

背景&#xff1a;需求是这样的&#xff0c;原始文件是txt文件&#xff08;每天300个文件&#xff09;&#xff0c;最终想要的结果是每天将txt中的数据加载到es中&#xff0c;开始的想法是通过logstash加载数据到es中&#xff0c;但是对logstash不太熟悉&#xff0c;不知道怎么讲…

基于SpringBoot的生鲜管理系统的设计与实现

背景 困扰交易市场的许多问题当中,生鲜交易管理一定是交易市场不敢忽视的一块。但是管理好生鲜交易又面临很多麻烦需要解决,例如有几个方面:第一,生鲜市场往往人数都比较多,如何保证能够管理到每一个商家,如何在工作琐碎,记录繁多的情况下将生鲜交易的当前情况反应给领导相关部…

柔顺机构学读书笔记1:悬臂梁变形

题目&#xff1a; 如图考虑悬臂梁&#xff0c;材料各向同性&#xff0c;即各个方向上的弹性模量和强度都相同。如果在x方向上作用一个可使最大应力等于屈服强度 S S S的力 F x F_x Fx​时&#xff0c; x x x轴方向的变形为多少&#xff0c;书上给出了答案&#xff1a; 我们来验…

2022级云曦实验室考试(一)pwn

讲真&#xff0c;俺都不知道pwn是啥&#xff0c;等俺搜搜&#xff01; pwn简介&#xff1a; CTF中的pwn指的是通过通过程序本身的漏洞&#xff0c;编写利用脚本破解程序拿到主机的权限&#xff0c;这就需要对程序进行分析&#xff0c;了解操作系统的特性和相关漏洞&#xff0…

SHELL——流程控制条件判断

1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。 2、判断web服务是否运行 1&#xff09;、查看进程的方式判断该程序是否运行 2&#xff09;、通过查看端口的方式判断该程序是否运行&am…

数据分析真的很火吗?真的有很多企业需要这样的岗位吗?求大佬指点。

“我是去年毕业的&#xff0c;因为疫情影响&#xff0c;整个就业环境都很不好&#xff0c;很多企业都裁员了。加上疫情三年基本都是玩过去&#xff0c;也没啥一技之长&#xff0c;就业就更难了。听说现在做数据分析的人很多&#xff0c;我身边的朋友都在转行做数据分析。 其实…

【C++】哈希——unordered系列容器哈希概念哈希冲突

文章目录 1. unordered系列的关联式容器1.1 引言1.2 unordered_map的使用说明1.3 unordered_set的使用说明1.4 unordered_set和unordered_map的应用1.5 性能比较 2. 哈希概念3. 哈希函数4. 哈希冲突5. 哈希冲突的解决——开散列和闭散列5.1 闭散列5.2 开散列 1. unordered系列的…

Elasticsearch:Explicit mapping - 显式映射

显式映射相比较动态映射&#xff08;Dynamic mapping&#xff09;是需要我们在索引创建时就定义字段及其类型。这个和我们传统的 RDMS 数据库一样&#xff0c;在我们写入数据到数据库之前&#xff0c;我们需要工整地定义好每个字段及其类型和长度。Elasticsearch 既可以使用显式…

使用柔性数组重写MyString

hello&#xff0c;各位宝子&#xff0c;今天阿崽将使用c和柔性数组的方式重新去写String类 在开始本次知识前&#xff0c;首先给大家介绍下柔性数组这个buff特点&#xff1a; 结构中的柔性数组成员前面至少要包含一个其他成员 sizeof返回的这种结构大小不包括柔性数组的内存 …

数据结构课程设计——哈夫曼编/译码器

数据结构课程设计任务书 学生姓名&#xff1a; 专业班级&#xff1a;软件工程 指导教师&#xff1a; 工作单位&#xff1a; 题 目: 哈夫曼编/译码器 基础要求&#xff1a; &#xff08;1&#xff09;熟悉各种…

数字信号处理基础(二):FFT和IFFT的使用以及详细分析代码书写思路

目录 1. fft和ifft的原理1.1 fft1.2 ifft 2. 书写代码思路3. 完整代码4. 结果图 1. fft和ifft的原理 1.1 fft fft是快速傅里叶变换&#xff0c;是MATLAB中计算信号频谱的函数&#xff0c;使用方法是fft(x)&#xff0c;直接对信号x进行fft计算。 由于fft函数计算信号的频谱是0…

vue3与vue2共存环境搭建

1、全局安装vue2 npm install vue-cli -g2、自行在任意位置创建一个文件夹&#xff0c;局部安装vue3 npm初始化 npm initnpm初始化 提示&#xff1a; 初始化后 出现文件package.json 如果没有初始化 会报错&#xff0c;且文件夹中不会新增内容 3、局部安装vue3 npm install …

宏工科技“全面”发力CIBF,助推电池智造“高效提质”

5月16-18日&#xff0c;第十五届中国国际电池技术展览会&#xff08;CIBF2023&#xff09;在深圳盛大举行。宏工科技携电池材料与电池匀浆领域的创新产品和系统解决方案精彩亮相。 据了解&#xff0c;宏工科技在新能源行业的业务涉及电池材料整线产线、电池匀浆、电池回收三个…

R语言实践——rWCVP入门

rWCVP入门 介绍1. 访问到WCVP1.1 方法一1.2 方法二&#xff08;谨慎&#xff09; 2. WCVP数据筛选2.1 关于按分类单元筛选的说明2.2 关于按分布区域筛选的说明 笔者实践 介绍 世界维管植物名录&#xff08;WCVP&#xff09;是维管植物物种的全球共识。它提供了科学已知的> …

【C语言】结构体指针

结构体指针 结构体基础知识注意对于成员的赋值 结构体指针指向结构体变量的指针结构体指针与结构体成员指针用结构体指针引用结构体成员 结构体 基础知识 初识结构体&#xff0c;可以先看这篇浅显易懂的文章结构体–基础篇 所谓结构体&#xff0c;是一组类型可以不同的相关变…

怎么把录音转文字?推荐你这三款工具

随着科技不断发展&#xff0c;录音转文字的技术也逐渐被广泛应用于各种场景中。其中最常见的一种就是会议记录。在日常工作中&#xff0c;会议是企业和组织中必不可少的一个环节&#xff0c;但在会议过程中的录音和记录往往需要花费大量的时间和精力。这个时候&#xff0c;我们…

基于MAC地址的ACL配置

基于MAC地址的ACL配置 【实验目的】 掌握基于MAC地址的标准ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 S1 e0/0 N/A N/A N/A e0/1 N/A N/A N/A PC1 N/…

架构-软件工程模块-2

系统分析 数据流图可能出案例题&#xff0c;状态转换图了解作用即可 用例图、类图选择题多&#xff0c;暴徒了解即可 #mermaid-svg-lGozbtkYJPEQF1eo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lGozbtkYJPEQF1e…

c++学习——c与c++const修饰的变量的区别

c语言下const修饰的变量 1、c语言下const修饰的变量都有空间 2. c语言的const修饰的全局变量具有外部链接属性 07 const修饰的变量.c #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h>const int a 10;//常…