当前位置: 首页 > 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(…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...