当前位置: 首页 > news >正文

算法42:天际线问题(力扣218题)---线段树

218. 天际线问题

城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回 由这些建筑物形成的 天际线 。

每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:

  • lefti 是第 i 座建筑物左边缘的 x 坐标。
  • righti 是第 i 座建筑物右边缘的 x 坐标。
  • heighti 是第 i 座建筑物的高度。

你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。

天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],...] ,并按 x 坐标 进行 排序 。关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。

注意:输出天际线中不得有连续的相同高度的水平线。例如 [...[2 3], [4 5], [7 5], [11 5], [12 7]...] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]

示例 1:

输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:
图 A 显示输入的所有建筑物的位置和高度,
图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。

示例 2:

输入:buildings = [[0,2,3],[2,5,3]]
输出:[[0,3],[5,0]]

分析:

这一题看起来很复杂,其实掌握了算法40和算法41的知识点以后,分析起来还是很容易的。

1. 首先,我们观察图片发现,天际线搜集的就是每个建筑物的开始坐标和结束坐标。开始坐标就是建筑物的高度。而结束坐标默认搜集高度为0.

2. 如果有第二个建筑物和第一个建筑物有部分重叠,那么第二个建筑物比第一个建筑物高的话,就搜集第二个建筑物开始位置的横坐标和高度;

如果第二个建筑物比第一个建筑物更宽,说明第二个建筑物把第一个建筑物个住当住了,第二个建筑物比第一个建筑物又高又宽,那么直接放弃第一个建筑物搜集的结束点的横坐标和高度信息;搜集第二个建筑物的坐标和高度替换第一个建筑物的结束点信息。当然,第二个建筑物的结束点高度为0.

3. 建筑物给的顺序,是X轴排好序的。因此,每添加一个建筑物,就搜集一下开始点。结束点是需要判断的;

4. 利用线段树的知识点,首先对X轴坐标进行搜集并确认区间;其次,每一个建筑物都有区间,区间的结束点都默认为0;0代表不更新,如果当前区间被之前的建筑物占领了位置,还保留之前的建筑物坐标信息。

5. 以本题第一个案例来分析,首先搜集X轴坐标并划分区间信息:

有了以上信息,我们接下来就是逐步推导的过程了:

由于天际点搜集的是每个区间的开始位置和结束位置;因此,存在连续、重复的信息应该忽略掉后一个重复值。最终搜集的是:

参照上图,根据区间获取X轴坐标值:

1 区间的 10       1区间对应X轴的2, 因此最终是 [2, 10]

2 区间的 15        2区间对应X轴的3, 因此最终是 [3, 15]

4 区间的 12        4区间对应X轴的7, 因此最终是 [7, 12]

6 区间的 0          6区间对应X轴的12, 因此最终是 [12, 0]

7 区间的 10        7区间对应X轴的15, 因此最终是 [15, 10]

9 区间的 8          9区间对应X轴的20, 因此最终是 [20, 8]

10 区间的 0        10区间对应X轴的24, 因此最终是 [24, 0]

最终结果就是 [[2, 10], [3, 15], [7, 12], [12, 0], [15, 10], [20, 8], [24, 0]]

代码实现:

package code04.线段树_02;import java.util.*;//力扣 216,天际线问题 https://leetcode.cn/problems/the-skyline-problem/
public class Code03_SkyLine_2 {class SegmentTree {int[] lines;SegmentTree(int size){lines = new int[size * 4];}//不使用懒更新public void add(int left,int right,int curIndex,int start,int end,int value){//叶子节点if (left == right) {if (left != end) {lines[curIndex] = value > lines[curIndex] ? value : lines[curIndex];}return;}int mid = (left + right)/2;if (start <= mid) {add(left, mid, curIndex * 2, start, end, value);}if (end > mid) {add(mid + 1, right, curIndex * 2 + 1, start, end, value);}}public void query(int left,int right,int curIndex,Map map,List<List<Integer>> list){//叶子节点if (left == right) {/*** 1. 为空直接放入* 2. 不为空,需要判断list最后一个元素*    即最后一个元素的下标为1的位置的值,是否与innerList*    下标为1的值相等。相等则排除,否则加入*/if (list.isEmpty()|| (!list.isEmpty()&& list.get(list.size() - 1) != null&& list.get(list.size() - 1).get(1) != lines[curIndex])) {List<Integer> innerList = new ArrayList<>();//横坐标innerList.add((Integer) map.get(left));//纵坐标innerList.add( lines[curIndex]);list.add(innerList);}return;}int mid = (left + right)/2;query(left, mid, curIndex * 2, map, list);query(mid + 1, right, curIndex * 2 + 1, map, list);}}//根据x轴,按照从左到右、从大到小的顺序编制区间下标public HashMap<Integer, Integer> index(int[][] positions){TreeSet<Integer> pos = new TreeSet<>();//离散化过程,统计开始、结束区间的坐标。//不管数组长度为多少,最终都是落在这些区间中的for (int[] arr : positions) {pos.add(arr[0]);pos.add(arr[1]);}int index = 1;HashMap<Integer, Integer> map = new HashMap<>();//给每个下标编个index,从1开始; 模拟原始线段树的原始数组中给每个元素添加下标的逻辑for (Integer key : pos) {map.put(key, index++);}return map;}//根据区间下标找对应的x轴坐标值public HashMap<Integer, Integer> reverseKeyValue (HashMap<Integer, Integer> map){HashMap reverseMap = new HashMap();for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {int key = (int) iterator.next();int value = map.get(key);reverseMap.put(value, key);}return reverseMap;}public List<List<Integer>> getSkyline(int[][] buildings) {//获取到了X轴上对应的下标HashMap<Integer, Integer> map = index(buildings);int size = map.size();SegmentTree tree = new SegmentTree(size);//原始数组的范围int left = 1;int curIndex = 1;int right = size;for (int[] arr : buildings) {//任务的范围int start = map.get(arr[0]);int end = map.get(arr[1]);int value = arr[2];tree.add(left, right, curIndex, start, end, value);}List<List<Integer>> list = new ArrayList<>();HashMap<Integer, Integer> reverseMap = reverseKeyValue(map);tree.query(left, right, curIndex, reverseMap, list);return list;}public static void main(String[] args) {int[][] buildings = {{2,9,10},{3,7,15},{5,12,12},{15,20,10},{19,24,8}};Code03_SkyLine_2 ss = new Code03_SkyLine_2();System.out.println(ss.getSkyline(buildings));}
}

力扣测试结果:

一顿操作猛如虎,结果只打败了 5%,说明代码不够优秀,还需要优化。

优化:

目测我刚刚分析的图片

1、区间的最后一个高度根本就不做考虑,也就是说线段树更新 1 - N,实际上关注的就是 1 到 (N-1)的范围; 这样的话,add方法内部的 

if (left == right) {if (left != end) {lines[curIndex] = value > lines[curIndex] ? value : lines[curIndex];}return;
}

就可以直接去掉  if (left != end)  逻辑判断了。

2. 我们每添加一个建筑物,就递归到子节点。虽然线段树的时间复杂度为O(logN). 但是,执行1次和执行10次这样的时间复杂度方法,时间还是不一样的。因此,需要对目前的add方法进行优化,线段树的懒更新必须加进去

优化代码:

package code04.线段树_02;import java.util.*;//力扣 216,天际线问题 https://leetcode.cn/problems/the-skyline-problem/
public class Code03_SkyLine_2_opt {class SegmentTree {int[] lazy;SegmentTree(int size){lazy = new int[size * 4];}//不使用懒更新public void add(int left,int right,int curIndex,int start,int end,int value){if (start <= left && right <= end) {lazy[curIndex] = value > lazy[curIndex] ? value : lazy[curIndex];return;}int mid = (left + right)/2;pushDown(curIndex);if (start <= mid) {add(left, mid, curIndex * 2, start, end, value);}if (end > mid) {add(mid + 1, right, curIndex * 2 + 1, start, end, value);}}public void pushDown (int curIndex){if (lazy[curIndex] != 0) {lazy[curIndex*2] = lazy[curIndex] > lazy[curIndex * 2] ? lazy[curIndex] : lazy[curIndex * 2] ;lazy[curIndex*2+1] = lazy[curIndex] > lazy[curIndex * 2 + 1] ? lazy[curIndex] : lazy[curIndex * 2 + 1] ;lazy[curIndex] = 0;}}public void query(int left,int right,int curIndex,Map map,List<List<Integer>> list){//叶子节点if (left == right) {if (list.isEmpty()|| (!list.isEmpty()&& list.get(list.size() - 1) != null&& list.get(list.size() - 1).get(1) != lazy[curIndex])) {List<Integer> innerList = new ArrayList<>();//横坐标innerList.add((Integer) map.get(left));//纵坐标innerList.add(lazy[curIndex]);list.add(innerList);}return;}int mid = (left + right)/2;pushDown(curIndex);query(left, mid, curIndex * 2, map, list);query(mid + 1, right, curIndex * 2 + 1, map, list);}}//根据x轴,按照从左到右、从大到小的顺序编制区间下标public HashMap<Integer, Integer> index(int[][] positions){TreeSet<Integer> pos = new TreeSet<>();//离散化过程,统计开始、结束区间的坐标。//不管数组长度为多少,最终都是落在这些区间中的for (int[] arr : positions) {pos.add(arr[0]);pos.add(arr[1]);}int index = 1;HashMap<Integer, Integer> map = new HashMap<>();//给每个下标编个index,从1开始; 模拟原始线段树的原始数组中给每个元素添加下标的逻辑for (Integer key : pos) {map.put(key, index++);}return map;}//根据区间下标找对应的x轴坐标值public HashMap<Integer, Integer> reverseKeyValue (HashMap<Integer, Integer> map){HashMap reverseMap = new HashMap();for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {int key = (int) iterator.next();int value = map.get(key);reverseMap.put(value, key);}return reverseMap;}public List<List<Integer>> getSkyline(int[][] buildings) {//获取到了X轴上对应的下标HashMap<Integer, Integer> map = index(buildings);int size = map.size();SegmentTree tree = new SegmentTree(size);//原始数组的范围int left = 1;int curIndex = 1;int right = size;for (int[] arr : buildings) {//任务的范围int start = map.get(arr[0]);int end = map.get(arr[1]);int value = arr[2];//天际线的区间最后一个x坐标的高度信息根本不做考虑,默认就是0.// 因此,start - end的区间,实际考虑的知识 start - (end-1)的范围tree.add(left, right, curIndex, start, end - 1, value);}List<List<Integer>> list = new ArrayList<>();HashMap<Integer, Integer> reverseMap = reverseKeyValue(map);tree.query(left, right, curIndex, reverseMap, list);return list;}public static void main(String[] args) {//int[][] buildings = {{2,9,10},{3,7,15},{5,12,12},{15,20,10},{19,24,8}};//int[][] buildings = {{0,2,3},{2,5,3}};int[][] buildings = {{2,13,10},{10,17,25},{12,20,14}};Code03_SkyLine_2_opt ss = new Code03_SkyLine_2_opt();System.out.println(ss.getSkyline(buildings));}
}

测试结果:打败76%

分析这个问题并且实现第一版代码只花了半天时间,但是优化出第二版代码却花了一整天。

不管是什么算法和数据结构,光掌握原理是远远不够的。熟能生巧,多练、多思考,才能快速写出优秀的代码,这是不可缺少的流程。共勉之!

相关文章:

算法42:天际线问题(力扣218题)---线段树

218. 天际线问题 城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度&#xff0c;请返回 由这些建筑物形成的 天际线 。 每个建筑物的几何信息由数组 buildings 表示&#xff0c;其中三元组 buildings[i] [lefti, righti, heig…...

SpringBoot中使用Spring自带线程池ThreadPoolTaskExecutor与Java8CompletableFuture实现异步任务示例

场景 关于线程池的使用&#xff1a; Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)&#xff1a; Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)_executorservice executorservice executors.newfix-CSDN博客 Java中创建线程的方式…...

OpenCV/C++:点线面相关计算(二)

接续&#xff0c;继续更新 OpenCV/C:点线面相关计算_线面相交的点 代码计算-CSDN博客文章浏览阅读1.6k次&#xff0c;点赞2次&#xff0c;收藏12次。OpenCV处理点线面的常用操作_线面相交的点 代码计算https://blog.csdn.net/cd_yourheart/article/details/125626239 目录 1、…...

2024最新版鸿蒙HarmonyOS开发工具安装使用指南

2024最新版鸿蒙HarmonyOS开发工具安装使用指南 By JacksonML 0. 什么是鸿蒙Harmony OS&#xff1f; 华为鸿蒙系统&#xff08;HUAWEI Harmony OS&#xff09;&#xff0c;是华为公司在2019年8月9日于东莞举行的华为开发者大会&#xff08;HDC.2019&#xff09;上正式发布的分…...

Spring事务源码解析

Spring的事务属于逻辑事务。不是物理事务。 Spring并不直接管理事务&#xff0c;而是提供了多种事务管理器&#xff0c;它们将事务管理的职责委托给JDBC或者JTA等持久化机制所提供的相关平台框架的事务来实现。例如JDBC的事物管理器就是DataSourceTransactionManager。   Spr…...

71.Spring和SpringMVC为什么需要父子容器?

71.Spring和SpringMVC为什么需要父子容器&#xff1f; 就功能性来说不用子父容器也可以完成&#xff08;参考&#xff1a;SpringBoot就没用子父容器&#xff09; 1、所以父子容器的主要作用应该是划分框架边界。有点单一职责的味道。service、dao层我们一般使用spring框架 来…...

标准库 STM32+EC11编码器+I2C ssd1306多级菜单例程

标准库 STM32EC11编码器I2C ssd1306多级菜单例程 &#x1f4cc;原创项目来源于&#xff1a;https://github.com/AdamLoong/Embedded_Menu_Simple&#x1f4cd;相关功能演示观看&#xff1a;https://space.bilibili.com/74495335 单片机多级菜单v1.2 &#x1f449;本次采用的是原…...

通过 ChatGPT 的 Function Call 查询数据库

用 Function Calling 的方式实现手机流量包智能客服的例子。 def get_sql_completion(messages, model"gpt-3.5-turbo"):response client.chat.completions.create(modelmodel,messagesmessages,temperature0,tools[{ # 摘自 OpenAI 官方示例 https://github.com/…...

LLM(大语言模型)——大模型简介

目录 概述 发展历程 大语言模型的概念 LLM的应用和影响 大模型的能力、特点 大模型的能力 涌现能力&#xff08;energent abilities&#xff09; 作为基座模型支持多元应用的能力 支持对话作为统一入口的能力 大模型的特点 常见大模型 闭源LLM&#xff08;未公开源…...

SQLserver2008 r2 下载安装配置、使用、新建登录用户及通过Navicat远程连接

目录 一、下载 二、安装配置 1.安装 2.许可条款 3.安装程序支持文件 4.功能选择 5.实例配置 6.服务器配置 7.数据库引擎配置 8.Reporting Services 配置 9.安装进度 ​编辑 10.完成 三、使用 四、新建登录用户 1.新建登录名 2.常规 3.服务器角色 4. 用户映…...

linux code server 网页版的vscode

下载code-server安装包 挑选一个版本的包 https://github.com/coder/code-server/releases 找个amd64.deb包 wget http://…code-server_4.21.0-rc.1_amd64.deb 系统安装deb包 dpkg -i code-server_4.21.0-rc.1_amd64.deb配置外网访问与密码 可以先运行一下code-server&am…...

【leetcode100-086到090】【动态规划】一维五题合集2

【单词拆分】 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 思路&#xff1a; 首先&#xff0c;我…...

关闭Ubuntu 默认开启的自动安全更新

简介 Ubuntu 和 Debian 应该从很早版本开始预装unattended-upgrades 包&#xff0c;并开启自动更新有安全问题的软件包。 &#xff08;最小化安装不会安装此包&#xff09; 有什么影响&#xff1f; 如果软件包有安全漏洞&#xff0c;Ubuntu发布更新包后会自动安装更新并重启…...

python统计文本 2022年9月青少年电子学会等级考试 中小学生python编程等级考试二级真题答案解析

目录 python统计文本 一、题目要求 1、编程实现 2、输入输出...

HomeAssistant系统添加HACS插件商店与远程控制家中智能家居

文章目录 基本条件一、下载HACS源码二、添加HACS集成三、绑定米家设备 ​ 上文介绍了如何实现群晖Docker部署HomeAssistant&#xff0c;通过内网穿透在户外控制家庭中枢。本文将介绍如何安装HACS插件商店&#xff0c;将米家&#xff0c;果家设备接入 Home Assistant。 基本条件…...

计算huggingface模型占用硬盘空间的实战代码

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...

Leetcode 3031. Minimum Time to Revert Word to Initial State II

Leetcode 3031. Minimum Time to Revert Word to Initial State II 1. 解题思路2. 代码实现 题目链接&#xff1a;3031. Minimum Time to Revert Word to Initial State II 1. 解题思路 这一题就是一个z算法的题目&#xff0c;算是比较套路的题目了。 关于z算法&#xff0c…...

游戏后端如何实现服务器之间的负载均衡?

在当今的游戏行业中&#xff0c;随着游戏用户数量的不断增加&#xff0c;如何实现服务器之间的负载均衡成为了一个亟待解决的问题。游戏后端作为游戏的重要组成部分&#xff0c;承载着游戏逻辑处理和数据存储等功能&#xff0c;因此游戏后端的负载均衡问题尤为重要。本文将详细…...

es6中标签模板

之所以写这篇文章&#xff0c;是因为标签模板是一个很容易让人忽略的知识点 首先我们已经非常熟悉模板字符串的使用方法 const name "诸葛亮" const templateString hello, My name is ${name}标签模板介绍 这里的标签模板其实不是模板&#xff0c;而是函数调用…...

二级C语言笔试1

(总分96,考试时间90分钟) 一、选择题 下列各题A)、B)、C)、D)4个选项中&#xff0c;只有1个选项是正确的。 1. 有以下程序&#xff1a; void sum(int a[]) a[0]a[-1]a[1]; main() int a[10]1,2,3,4,5,6,7,8,9,10; sum(&a[2]); printf(…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解

一、前言 在HarmonyOS 5的应用开发模型中&#xff0c;featureAbility是旧版FA模型&#xff08;Feature Ability&#xff09;的用法&#xff0c;Stage模型已采用全新的应用架构&#xff0c;推荐使用组件化的上下文获取方式&#xff0c;而非依赖featureAbility。 FA大概是API7之…...

倒装芯片凸点成型工艺

UBM&#xff08;Under Bump Metallization&#xff09;与Bump&#xff08;焊球&#xff09;形成工艺流程。我们可以将整张流程图分为三大阶段来理解&#xff1a; &#x1f527; 一、UBM&#xff08;Under Bump Metallization&#xff09;工艺流程&#xff08;黄色区域&#xff…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇

根据 QYResearch 发布的市场报告显示&#xff0c;全球市场规模预计在 2031 年达到 9848 万美元&#xff0c;2025 - 2031 年期间年复合增长率&#xff08;CAGR&#xff09;为 3.7%。在竞争格局上&#xff0c;市场集中度较高&#xff0c;2024 年全球前十强厂商占据约 74.0% 的市场…...