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

news2024/9/23 15:30:21

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%

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

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

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

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

相关文章

中国大学生计算机设计大赛与大数据应用主题赛

中国大学生计算机设计大赛 与大数据应用主题赛 中国大学生计算机设计大赛&#xff08;简称“大赛”或4C&#xff09;始筹于2007年&#xff0c;首届于2008年&#xff0c;已经举办了16届80场赛事。是我国高校面向本科生最早的赛事之一&#xff0c;由教育部计算机类教指委发起举…

Android Button background 失效

问题 Android Button background 失效 详细问题 笔者开发Android项目&#xff0c;期望按照 android:background中所要求的颜色展示。 实际显示按照Android 默认颜色展示 解决方案 将xml的Button 组件修改为<android.widget.Button> 即将代码 <Buttonandroid:l…

有趣的CSS - css loading动画

Loading动画 整体效果核心代码html 代码&#xff1a;css 部分代码&#xff1a; 完整代码如下html 页面&#xff1a;css 样式&#xff1a;页面渲染效果&#xff1a; 整体效果 这个 Loading 效果主要用 css3 的 animation 属性配合 border 属性来实现的。 可以用作在下拉列表 Loa…

如何写好论文——(14)写作时如何定义和描述研究目标

当我们有了一个具体的细化的研究目标之后&#xff0c;我们需要把它清晰准备描述给我们的读者&#xff0c;并用它来指导我们的研究活动。 一、有方向 我们所选择的具体的研究目标一定要有明确的目标指向&#xff0c;那就是我们的长远目标。 二、有边界 我们要明确自己的研究…

在WORD中设置公式居中编号右对齐设置方式

1 软件环境 Office Microsoft Office LTSC 专业增强版2021 2 最终效果 3 操作步骤 编辑公式&#xff1b;光标定位到公式的最后&#xff08;不是行的最后&#xff09;&#xff1b;输入#编号光标定位在公式最后&#xff08;不是行的最后&#xff09;&#xff0c;按Enter键回车…

【计算机视觉】目标检测 |滑动窗口算法、YOLO、RCNN系列算法

一、概述 首先通过前面对计算机视觉领域中的卷积神经网络进行了解和学习&#xff0c;我们知道&#xff0c;可以通过卷积神经网络对图像进行分类。 如果还想继续深入&#xff0c;会涉及到目标定位(object location)的问题。在图像分类的基础上(Image classification)的基础上…

JAVA方法引用:

方法引用的出现原因在使用Lambda表达式的时候&#xff0c;我们实际上传递进去的代码就是一种解决方案&#xff1a;拿参数做操作那么考虑一种情况&#xff1a;如果我们在Lambda中所指定的操作方案&#xff0c;已经有地方存在相同方案&#xff0c;那是否还有必要再写重复逻辑呢&a…

SpringBoot整合ElasticSearch7.6.2入门

一、前期准备 ElasticSearch作为基于Lucene的一款分布式全文检索服务器&#xff0c;可以通过暴露restfulAPI来操作索引、搜索&#xff0c;具备有实时搜索、稳定、可靠、快速、安装使用方便等特点&#xff0c;是目前使用最广泛的企业级搜索引擎。 本期的目的就是——在springBoo…

前端工程化之:webpack1-13(内置插件)

目录 一、内置插件 1.DefinePlugin 2.BannerPlugin 3.ProvidePlugin 一、内置插件 所有的 webpack 内置插件都作为 webpack 的静态属性存在的&#xff0c;使用下面的方式即可创建一个插件对象&#xff1a; const webpack require("webpack")new webpack.插件…

线性表 —— 链表

与数组对比 ◼ 数组&#xff1a;  要存储多个元素&#xff0c;数组&#xff08;或选择链表&#xff09;可能是最常用的数据结构。  我们之前说过&#xff0c;几乎每一种编程语言都有默认实现数组结构。 ◼ 但是数组也有很多缺点&#xff1a;  数组的创建通常需要申请一…

【EVP】Explicit Visual Prompting for Low-Level Structure Segmentations

目录 &#x1f347;&#x1f347;0.简介 &#x1f337;&#x1f337;1.研究动机 &#x1f34b;&#x1f34b;2.主要贡献 &#x1f353;&#x1f353;3.网络结构 &#x1f36d;3.1整体结构 &#x1f36d;3.2高频分量计算 &#x1f36d;3.3显示视觉提示EVP &#x1f342;&…

数学电路与电子工程2(MEE)—— 时序电路的寄存器和工作频率

1. 基本的数字逻辑存储元件&#xff1a;D锁存器和D触发器 D锁存器&#xff08;Verrou ou D latch&#xff09;&#xff0c;它是一个简单的存储设备&#xff0c;可以在使能信号&#xff08;E&#xff09;处于活动状态时存储一位数据。当E为高电平时&#xff0c;D锁存器的输出Q会…

React | Center 组件

在 Flutter 中有 Center 组件&#xff0c;效果就是让子组件整体居中&#xff0c;挺好用。 React 中虽然没有对应的组件&#xff0c;但是可以简单封装一个&#xff1a; index.less .container {display: flex;justify-content: center;align-items: center;align-content: ce…

java设计模式:策略模式

在平常的开发工作中&#xff0c;经常会用到不同的设计模式&#xff0c;合理的使用设计模式&#xff0c;可以提高开发效率&#xff0c;提高代码质量&#xff0c;提高代码的可拓展性和维护性。今天来聊聊策略模式。 策略模式是一种行为型设计模式&#xff0c;运行时可以根据需求动…

Stable diffusion使用和操作流程

Stable Diffusion是一个文本到图像的潜在扩散模型,由CompVis、Stability AI和LAION的研究人员和工程师创建。它使用来自LAION-5B数据库子集的512x512图像进行训练。使用这个模型,可以生成包括人脸在内的任何图像,因为有开源的预训练模型,所以我们也可以在自己的机器上运行它…

Windows下MySQL的界面安装

华子目录 下载MySQL安装MySQL配置MySQL配置环境变量检验MySQL是否安装成功 下载MySQL 首先我们在MySQL的官方下载MySQL https://www.mysql.com 点击download&#xff0c;开始下载 安装MySQL 下载完成后&#xff0c;我们双击msi文件 再点击next 之后我们先勾选I acc…

leetcode 1.两数之和(C++)DAY1(待补充哈希表法)

文章目录 1.题目描述示例提示 2.解答思路3.实现代码结果4.总结 1.题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&…

堪称灾难级攻击的 UDP FLOOD洪水攻击,应该如何防护?

DDOS又称为分布式拒绝服务&#xff0c;全称是Distributed Denial of Service。DDOS本是利用合理的请求造成资源过载&#xff0c;导致服务不可用&#xff0c;从而造成服务器拒绝正常流量服务。就如酒店里的房间是有固定的数量的&#xff0c;比如一个酒店有50个房间&#xff0c;当…

Java语法学习线程基础

Java语法学习线程基础 大纲 概念创建线程线程终止常用方法用户线程和守护线程线程的七大状态线程的同步互斥锁线程死锁释放锁 具体案例 1.概念 2. 创建线程 第一种&#xff1a; class Cat extends Thread {int time 0;Overridepublic void run() {while (true) {System.o…

计算机网络_1.5 计算机网络的性能指标

1.5 计算机网络的性能指标 一、总览二、常用的八个计算机网络性能指标1、速率&#xff08;1&#xff09;数据量&#xff08;2&#xff09;速率&#xff08;3&#xff09;数据量与速率中K、M、G、T的数值辨析&#xff08;4&#xff09;【练习1】计算发送数据块的所需时间 2、带宽…