线段最大重合区域问题

news/2024/5/20 7:38:30 标签: 算法, 数据结构, 牛客, , 线段树

线段最大重合区域问题

作者:Grey

原文地址:

博客园:线段最大重合区域问题

CSDN:线段最大重合区域问题

题目描述

牛客-线段重合-连接点算重合区域

主要思路

暴力解法

第一步,首先得到所有线段开始位置的最小值(假设为 min)和结束位置的最大值(假设为 max),组成了一个[min....max]区间;

第二步,在[min...max]区间内按单位 1 等分,对于每个等分位置的点,看看覆盖这个点的线段条数,最多线段覆盖的点,其线段条数就是答案。

完整代码如下

public static int maxCover3(int[][] lines) {
     int min = lines[0][0];
     int max = lines[0][1];
     for (int[] line : lines) {
            min = Math.min(min, Math.min(line[0], line[1]));
            max = Math.max(max, Math.max(line[0], line[1]));
     }
     int cover = 0;
     int maxCover = 0;
     for (int i = min; i <= max; i++) {
         for (int[] line : lines) {
               // 这里要注意 如果[1,2] ,[2, 3] 中2 算一个重合点的话,
               // 则条件为:line[0] <= i && line[1] >= i
               // 如果不算的话,line[0] <= i+0.5 && line[1] >= i + 0.5
               if (line[0] <= i && line[1] >= i) {
                    cover++;
                }
         }
         maxCover = Math.max(cover, maxCover);
         cover = 0;
      }
      return maxCover;
}

暴力解法时间复杂度O((max-min)*N)

解法

准备小根中存线段信息,遍历每一个线段,得到其开始位置 L 和结束位置 R,规则是:

  1. 每个线段按开始位置 L 从小到大排序;
  2. 如果为空,把线段结束位置 R 进入
  3. 不为空,则遍历到线段 L 位置和顶元素(假设为 M )比较,如果顶元素小于 L,则结算一次中元素大小,记为 size,然后弹出顶元素,直到顶元素小于 L,然后把 L 加入;
  4. 每次生成的 size 取最大值即为最大重合了几个线段。

关键代码如下

  public static int maxCover(int[][] lines) {
    Arrays.sort(lines, Comparator.comparingInt(o -> o[0]));
    PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(o -> o[1]));
    int max = 0;
    for (int[] line : lines) {
      // 这里要注意
      // 如果[1,2] ,[2, 3] 中2 算一个重合点的话,heap.peek()[1] < line[0]
      // 如果不算的话,heap.peek()[1] <= line[0]
      while (!heap.isEmpty() && heap.peek()[1] < line[0]) {
        heap.poll();
      }
      heap.add(line);
      max = Math.max(max, heap.size());
    }
    return max;
  }

解法的时间复杂度是O(N*logN)

线段树解法

线段树说明见:使用线段树解决数组任意区间元素修改问题

主要思路如下:

第一步:先做离散化处理,这是线段树类型问题的常规操作;

  // 离散化
  public static HashMap<Integer, Integer> index(int[][] lines) {
    TreeSet<Integer> set = new TreeSet<>();
    for (int[] line : lines) {
      set.add(line[0]);
      set.add(line[1]);
    }
    HashMap<Integer, Integer> map = new HashMap<>(set.size());
    int count = 0;
    for (Integer i : set) {
      map.put(i, ++count);
    }
    return map;
  }

举例说明:

假设线段为[1,5][7,9][89,97][32,1077][2044,100039],如果不做离散化处理,要覆盖的线段长度需要从 1 一直到 100039,浪费空间,所以,离散化以后,每个点映射关系如下

1 -> 1
5 -> 2
7 -> 3
9 -> 4
32 -> 5
89 -> 6
97 -> 7
1077 -> 8
2044 -> 9
100039 -> 10

线段就可以转换成

[1,5] -> [1,2]
[7,9] -> [3,4]
[89,97] -> [6,7]
[32,1077] -> [5,8]
[2044,100039] -> [9,10]

离散化的结果,得出的结论和原线段列表得出的结论是一样的。

第二步:离散化后,将每个线段的开始和结束位置加入线段数据;

第三步:在线段树原有逻辑的基础上,增加queryMax方法,用于计算重叠的区间个数,记录一个最大值即可。

线段树解法的完整代码如下,时间复杂度是O(N*logN):

  // 线段树解法
  public static int maxCover2(int[][] lines) {
    HashMap<Integer, Integer> map = index(lines);
    int N = map.size();
    SegmentTree tree = new SegmentTree(N);
    long max = 0;
    for (int[] line : lines) {
      int L = map.get(line[0]);
      int R = map.get(line[1]);
      tree.add(L, R, 1, 1, N, 1);
      long l = tree.queryMax(L, R, 1, N, 1);
      max = Math.max(l, max);
    }
    return (int) max;
  }

  // 离散化
  public static HashMap<Integer, Integer> index(int[][] lines) {
    TreeSet<Integer> set = new TreeSet<>();
    for (int[] line : lines) {
      set.add(line[0]);
      set.add(line[1]);
    }
    HashMap<Integer, Integer> map = new HashMap<>(set.size());
    int count = 0;
    for (Integer i : set) {
      map.put(i, ++count);
    }
    return map;
  }

  // [1...3],[2..6],[4..9],问:哪个区间描的最多,可以用线段树(注意离散化,注意在范围内+1以后,执行的不是querySum而是queryMax)
  // 注意:不管什么线段,开始位置排序,线段开始位置越早,越先处理
  public static class SegmentTree {
    private int MAXN;
    private int[] arr;
    private int[] max;
    private int[] lazy;

    public SegmentTree(int N) {
      MAXN = N + 1;
      arr = new int[MAXN];
      int v = MAXN << 2;
      lazy = new int[v];
      max = new int[v];
    }

    private void pushUp(int rt) {
      max[rt] = Math.max(max[rt << 1], max[(rt << 1) | 1]);
    }

    private void pushDown(int rt, int ln, int rn) {
      if (lazy[rt] != 0) {
        max[rt << 1] += lazy[rt];
        max[(rt << 1) | 1] += lazy[rt];
        lazy[rt << 1] += lazy[rt];
        lazy[(rt << 1) | 1] += lazy[rt];
        lazy[rt] = 0;
      }
    }

    public void add(int L, int R, int C, int l, int r, int rt) {
      if (L <= l && R >= r) {
        lazy[rt] += C;
        max[rt] += C;
        return;
      }
      int mid = (l + r) >> 1;
      pushDown(rt, mid - l + 1, r - mid);
      if (L <= mid) {
        add(L, R, C, l, mid, rt << 1);
      }
      if (R > mid) {
        add(L, R, C, mid + 1, r, (rt << 1) | 1);
      }
      pushUp(rt);
    }

    public long queryMax(int L, int R, int l, int r, int rt) {
      if (L <= l && R >= r) {
        return max[rt];
      }
      int mid = (l + r) >> 1;
      pushDown(rt, mid - l + 1, r - mid);
      long left = Integer.MIN_VALUE;
      long right = Integer.MIN_VALUE;
      if (L <= mid) {
        left = queryMax(L, R, l, mid, rt << 1);
      }
      if (R > mid) {
        right = queryMax(L, R, mid + 1, r, (rt << 1) | 1);
      }
      return Math.max(left, right);
    }
  }

  public static class Line {
    public int start;
    public int end;

    public Line(int s, int e) {
      start = s;
      end = e;
    }
  }

类似问题

牛客-线段重合

主要思路:本题和上题唯一的区别就是:本题中的线段的连接点不算重合部分。思路和上题完全一样。

更多

算法数据结构笔记


http://www.niftyadmin.cn/n/1647786.html

相关文章

java基础之抽象类(abstract)和接口(interface)

一. 抽象类的特性&#xff08;语法规则&#xff09; 抽象类不能被实例化 注&#xff1a;为什么抽象类不能被实例化&#xff1f; 从语义上来解释&#xff0c;抽象类表示的从具体类型抽象出来的类型&#xff0c;例如苹果类、香蕉类和橘子类是具体类&#xff0c;而水果类则是抽象…

Python的三种可变参数

初学python没多久&#xff0c;对python中函数的可变参数有点头晕&#xff0c;查阅了stackflow&#xff0c;现总结一下可变参数 可变参数应该最简单&#xff0c;在C/C和Java等语言中都有&#xff0c;就是用*号来表示&#xff0c;例如 def testArg(*arg) 你可以传入任意多个元素&…

Linux 下安装 Nginx

Linux 下安装 Nginx 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;Linux 下安装 Nginx CSDN&#xff1a;Linux 下安装 Nginx 版本 Nginx&#xff1a;1.22 下载地址: nginx-1.22.0 操作系统&#xff1a;CentOS-7 或 Debian 10&#xff0c;本文以 CentO…

java高级编程之内部类(全)

1. 内部类与封装&#xff0c;内部类的优势&#xff1f; 内部类的定义&#xff1a;在一个类的内部定义的类成为内部类 提到内部类&#xff0c;就不得不提java三大特性之一的封装特性&#xff0c;关于封装的介绍&#xff0c;可参考此文&#xff1a;封装概念和特点 内部类与封装…

Ubuntu 蓝牙鼠标一段时间失效的问题

问题&#xff1a; 我有一个小巧的蓝牙鼠标&#xff0c;但有一个问题。 当它不使用一段时间时&#xff0c;它会关闭。 好的我得按按钮把它打开。 但是我发现&#xff0c;在我在蓝牙小程序下单击"连接"之前&#xff0c;它不会再被Ubuntu识别出来。 我有一个蓝牙touchpa…

汉诺塔进阶问题

汉诺塔进阶问题 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;汉诺塔进阶问题 CSDN&#xff1a;汉诺塔进阶问题 题目描述 牛客-汉诺塔II 有一个int数组arr其中只含有1、2和3&#xff0c;分别代表所有圆盘目前的状态&#xff0c;1代表左柱&#xff0c;2代…

如何阅读大型项目的代码?(转)

如何阅读大型项目的代码&#xff1f;(转) 本文转载自&#xff1a;http://blog.csdn.net/jk110333/article/details/7563718 Technorati 标签: 源码阅读作者&#xff1a;浩天之家出处&#xff1a;http://www.cnblogs.com/cherishui/本文版权归作者和博客园共有&#xff0c;欢迎转…

马尔科夫过程,马尔科夫奖励过程和马尔科夫决策过程

马尔科夫决策过程是强化学习中的一个基本框架&#xff0c;用来表示agent与环境的交互过程&#xff1a;agent观测得到环境的当前状态之后&#xff0c;采取动作&#xff0c;环境进入下一个状态&#xff0c;agent又得到下一个环境状态的信息&#xff0c;形成一个循环回路。 在理解…