【问题标题】:Check how many rectangles cover a point检查有多少矩形覆盖一个点
【发布时间】:2022-01-14 18:24:50
【问题描述】:

我有一个点列表[[1,2], [2,3], [1,5]],每个点代表一个尺寸为[0,0], [x, 0], [0, y], [x,y] 的矩形。 这里以[1,2], x =1 and y = 2 为例,所以矩形尺寸为[0,0], [1, 0], [0, 2], [1,y]

现在我有另一个点列表[[1,1], [1,4]]。现在我的任务是检查有多少矩形可以覆盖这些点。

For example: [[1,1], [1,4]

[1,1] is covered by [1,2], [2,3], [1,5] so 3.
[1,4] is covered by [1,5] only so 1.

这是我的程序:

public List<Integer> process(List<List<Integer>> inp1, List<List<Integer>> inp2) {
    List<Integer> list = new ArrayList<>();

    for (List<Integer> p1 : inp2) {
        int a = p1.get(0), b = p1.get(1);
        int count = 0;

        for (List<Integer> p2 : inp1) {
            int c = p2.get(0), d = p2.get(1);

            if (c >= a && d >= b)
                count++;
        }

        list.add(count);
    }

    return list;
}

该程序适用于小输入,因为时间复杂度为 O(N^2),但我的输入列表可能非常大,因此程序因超时错误而失败。如何改进这段代码?我正在寻找打破内部循环的条件或其他更好的方法来解决此任务。

约束:

Size of the input list is 1 to 10^5.

x coordinate of inp1 is 1 to 10^9
y coordinate of inp1 is 1 to 100

x coordinate of inp2 is 0 to 10^9
y coordinate of inp2 is 0 to 100

更新:

我已经尝试过@user1984 提到的方法,但是当输入范围非常高并出现超时错误时,代码仍然会失败。如何进一步改进此解决方案?

public static void main(String[] args) {
    System.out.println(process(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(2, 3), Arrays.asList(1, 5)),
            Arrays.asList(Arrays.asList(1, 1), Arrays.asList(1, 4))));
}

public static List<Integer> process(List<List<Integer>> inp1, List<List<Integer>> inp2) {
    Map<Integer, List<Integer>> map = new TreeMap<>();
    for (List<Integer> list : inp1) {
        int k = list.get(0);
        List<Integer> v = map.getOrDefault(k, new ArrayList<>());
        map.put(k, v);
        v.add(list.get(1));
    }
    List<Integer> keys = new ArrayList<>(map.keySet());
    for (int key : keys) {
        List<Integer> list = map.get(key);
        Collections.sort(list);
    }

    List<Integer> result = new ArrayList<>();
    for (List<Integer> list : inp2) {
        int x = list.get(0);
        int y = list.get(1);
        // Get the starting index for x coordinate
        int k = startsFrom(keys, x);
        if (k == -1) {
            result.add(0);
        } else {
            int count = 0;
            for (int i = k; i < keys.size(); i++) {
                List<Integer> values = map.get(keys.get(i));
                // Get the starting index for y coordinate
                int k1 = startsFrom(values, y);
                if (k1 != -1) {
                    count += values.size() - k1;
                }
            }
            result.add(count);
        }
    }
    return result;
}
// Get the index where target can be found in list
private static int startsFrom(List<Integer> list, int target) {
    int s = 0, e = list.size() - 1;
    int k = -1;
    while (s <= e) {
        int m = (s + e) / 2;
        if (list.get(m) < target) {
            s = m + 1;
        } else {
            k = m;
            e = m - 1;
        }
    }
    return k;
}

更新: 附加测试用例:

public static void main(String[] args) {
        System.out.println(process(Arrays.asList(a(6, 15), a(6, 12), a(9, 13), a(9, 16), a(10, 15), a(14, 15), 
                a(13, 14), a(15, 20), a(11, 16), a(10, 18), a(13, 12), a(10, 20),
                a(6, 16), a(5, 11), a(7, 10), a(13, 11), a(9, 12), a(14, 17), 
                a(9, 17), a(8, 16), a(11, 19), a(9, 11), a(10, 12), a(13, 17),
                a(8, 14), a(14, 12), a(12, 14), a(15, 12), a(5, 15), a(7, 18),
                a(13, 15), a(5, 16), a(7, 16), a(7, 11), a(14, 18), a(6, 18),
                a(12, 12), a(5, 14), a(8, 12), a(6, 17), a(6, 19), a(15, 18),
                a(13, 18), a(5, 12), a(9, 15), a(6, 20), a(14, 10), a(9, 18),
                a(12, 15), a(6, 11)), 
                
                Arrays.asList(a(4, 16), a(2, 6), a(7, 4), a(15, 9), a(15, 15), a(6, 13),
                        a(14, 10), a(8, 9), a(7, 1), a(11, 6), a(2, 6), a(11, 1), a(11, 0),
                        a(4, 4), a(3, 20), a(9,6), a(13,13), a(1,3), a(2,7),
                        a(4,10), a(14,18), a(2,9), a(0,3), a(12,6), a(14,10), a(9,9),
                        a(15,12), a(3,14), a(15,6), a(7,2), a(14,15), a(9,7),
                        a(1,12), a(13,15), a(0,9), a(15,20), a(6,6), a(12,0), a(5,13),
                        a(7,17), a(15,15), a(5,10), a(14,14), a(1,3), a(13, 8), a(9,19), a(12,9), a(15,4), a(0,5), a(8,16))));
        
    }
 
   private static List<Integer> a(int i, int j) {
        return Arrays.asList(i, j);
    }

对此的预期输出是:

[22, 50, 37, 3, 2, 31, 8, 33, 37, 19, 50, 19, 19, 50, 3, 30, 9, 50, 50, 50, 3, 50, 50, 17, 8, 30, 3, 33, 3, 37, 5, 30, 43, 8, 50, 1, 45, 17, 34, 12, 2, 50, 5, 50, 14, 3, 17, 3, 50, 14]

基于Mohaned El-haddad 提供的解决方案,我尝试了下面的代码,但此代码为我的Additional Test case 提供了错误的输出为[3, 2, 3, 3, 1, 2, 3, 8, 3, 8, 5, 5, 9, 8, 14, 17, 17, 17, 19, 19, 19, 30, 30, 30, 3, 33, 14, 37, 37, 37, 12, 31, 45, 34, 50, 22, 50, 50, 3, 33, 50, 50, 50, 50, 50, 43, 50, 50, 50, 50]

public static List<Integer> process(List<List<Integer>> rectangles, List<List<Integer>> points) {
    int[] freq_arr = new int[101];

    rectangles.sort((r1, r2) -> r2.get(0) - r1.get(0));

    points.sort((p1, p2) -> p2.get(0) - p1.get(0));

    int idx = 0;

    int N = rectangles.size();
    List<Integer> list = new ArrayList<>();
    for (List<Integer> point : points) {
        while (idx < N && rectangles.get(idx).get(0) >= point.get(0)) {
            add(freq_arr, rectangles.get(idx).get(1));
            idx++;
        }
        list.add(calc(freq_arr, point.get(1)));
    }
    return list;
}

private static int calc(int[] freq_arr, int b) {
    int count = 0;
    for (int i = b; i < freq_arr.length; i++) {
        count += freq_arr[i];
    }
    return count;
}

private static void add(int[] freq_arr, int y) {
    freq_arr[y]++;
}

【问题讨论】:

  • 你说的time out errors是什么意思?
  • 不要引用我的话,但我认为我们可以使用扫线。
  • @PM77-1,我的代码时间复杂度是 O(N^2),因为我的输入范围非常大,它花费的时间非常长,效率不高。我正在寻找一个可以打破内部循环或其他方法来降低时间复杂度的方法。
  • 我看到有人要求关闭我的帖子,我的问题有问题吗?
  • 输入列表的大小为 1 到 10^5 有两个输入列表。两者都适用吗?

标签: java algorithm


【解决方案1】:

您当前的解决方案有 TC O(n * m),其中 nm 是矩形数组和点数组的长度。

以下是 TC 方面可能的改进:

  1. 创建一个排序地图,其中键是矩形的x 坐标,值是具有xy 坐标数组。
  2. 确保地图按键递增排序。
  3. 遍历这些点。对于每个点,请执行以下操作:
  • 找到覆盖该点 x 坐标的最小键(x 坐标)。由于地图已排序,因此此操作是对数的。您可以确定此键之后的所有键也涵盖了该点。
  • 检查所有大于或等于找到的键的键的值。这些是 y 值。如果它们满足条件,请将它们添加到您的结果中。

改进您的算法:同时对地图的值进行排序。这样,找到满足 y 坐标的最小值也需要对数时间,您无需检查其余部分。您可以从值数组长度中减去该索引。

【讨论】:

  • 这不是line sweep algo吗?
  • 我提出的不是线扫描算法。我在想,如果我们处理一维,我们可以使用线扫描算法。但是由于我们还需要检查 y 坐标,所以它不起作用。但我不是 100% 确定。
  • 嘿@Someone 我浏览了您发布的文章。我对线扫描算法有一些不同的看法。你是对的。我的提议与线扫描算法有一些基本的相似之处。
  • Find the minimum key (x coordinate) that covers the x coordinate of that point. This operation is logarithmic since the map is sorted. You can be sure that all the keys after this key also cover the point. 一旦我得到了最小键,我怎样才能在映射中获取该键的索引以迭代其他剩余键。因为在Java中,map没有indexO方法。这同样适用于地图值。
  • 如果是这样,那就不要使用地图了。创建一个简单的类实例数组,该数组具有一个用于 x 的 int 和另一个用于 ys 的数组,该数组在一个矩形中具有该 x。然后使用基于类实例的 x 值的自定义比较器对数组进行排序。现在,您可以使用涵盖您的点的 x 来二进制搜索最小索引,并且也可以索引其余部分。您也可以使用数组数组而不是类数组。在这种方法中,您可以说内部数组的第一个元素是 x 坐标,其余的 y 坐标具有这样的 x。希望这是有道理的
【解决方案2】:

让我解释一个仅使用排序、频率数组和 2 个指针的简单解决方案。

  • 要使点 (a,b) 位于矩形 (x1,y1,x2,y2) 内,必须满足 4 个条件。

    1. a >= x1
    2. 一个
    3. b >= y1
    4. b

因为在这个问题中 x1=0 and y1=0 and a>=0
那么条件 1 和 3 总是得到满足。
现在我们需要检查每个查询点有多少矩形满足剩余的两个条件。
让我们将 x2 重命名为 x 并将 y2 重命名为 y,因为其他值始终为零。
1) x >= 一个
2) y >= b

看看约束你会发现 MAX_Y = 100 我们将使用它来检查是否满足第二个条件。
我们怎样才能轻松解决这个问题?
让我们创建一个 freq_arr[MAX_Y+1];
当我们想向我们的 freq_arr 添加一个新的 rectangle(x,y) 时,我们会这样做
void add(freq_arr,y){
    freq_arr[y]++;
}

当有一个点(a,b)
当我们想查询有多少个矩形的 y >= b 时,我们可以这样做:

void calc(freq_arr,b){
    int count =0;
    for(int i = b;i<=MAX_Y;i++)count+=freq_arr[i];
    return count;
}

这样我们就可以检查 O(MAX_Y) 中是否满足条件 (2)。

让我们做一个小技巧来帮助我们确保条件(1)总是被满足
  1. 按照 x 的降序对所有矩形进行排序。

  2. 按 x 的降序对所有点进行排序。

  3. 让我们创建一个指向 rectangles 数组的指针,并将其命名为 rectangle_idx_to_be_add 并将其初始化为零。

  4. 让我们遍历点数组以分别回答每个点。

    for(auto point : points){
       // add all rectangles that have x>=a to freq_arr
       while(rectangle_idx_to_be_added <N &&
       rectangles[rectangle_idx_to_be_added].x >= point.a){
           add(freq_arr,rectangles[rectangle_idx_to_be_added].y);
           rectangle_idx_to_be_added++;
       }
       // at this point all the points in the the freq_arr have x>=a 
       // and all we need to check is how many y >= b
       // which we can do easily using the freq_arr
       ans[point.query_idx] = calc(freq_arr,point.b);
    }
    

Time complexity O(NlogN + N*MAX_Y)

NlogN 用于排序,N*MAX_Y 用于回答查询

【讨论】:

  • 我厌倦了你的方法并再次更新了我的帖子。它为我最近发布的更新的测试用例提供了错误的输出。此解决方案将输出为[3, 2, 3, 3, 1, 2, 3, 8, 3, 8, 5, 5, 9, 8, 14, 17, 17, 17, 19, 19, 19, 30, 30, 30, 3, 33, 14, 37, 37, 37, 12, 31, 45, 34, 50, 22, 50, 50, 3, 33, 50, 50, 50, 50, 50, 43, 50, 50, 50, 50] 但实际输出应为[22, 50, 37, 3, 2, 31, 8, 33, 37, 19, 50, 19, 19, 50, 3, 30, 9, 50, 50, 50, 3, 50, 50, 17, 8, 30, 3, 33, 3, 37, 5, 30, 43, 8, 50, 1, 45, 17, 34, 12, 2, 50, 5, 50, 14, 3, 17, 3, 50, 14] 如何解决此问题。
  • 输出是正确的,你只是没有在排序之前保存点的初始顺序,所以你把答案放在错误的顺序,所以而不是这一行
    list.add(calc (freq_arr, point.get(1))
    使用 list[point.get(2)] = calc(freq_arr, point.get(1));
    其中 point.get(2) 是输入中点的索引。
【解决方案3】:

我认为您可以通过使用特殊的数据结构来简化问题,例如TreeSet.

另一个明显的方法是您必须创建一个数据结构,以有效地检索所需的点。

我提议使用TreeSet 有效地检索给定范围内的所有点,然后使用两个HashMap 通过它们的坐标有效地接收点。

public class Main {

    public static void main(String[] args) {
        List<List<Integer>> inp1 = List.of(List.of(1, 2), List.of(2, 3), List.of(1, 5));
        List<List<Integer>> points = List.of(List.of(1, 1), List.of(1, 4));
        List<Integer> counts = process(inp1, points);
        System.out.println(counts); // [3, 1]
    }

    private static final class Point {

        private final int x;
        private final int y;
        private int count;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

    }

    public static List<Integer> process(List<List<Integer>> inp1, List<List<Integer>> inp2) {
        return processPoints(
                inp1.stream()
                    .map(i -> new Point(i.get(0), i.get(1)))
                    .collect(Collectors.toList()),
                inp2.stream()
                    .map(i -> new Point(i.get(0), i.get(1)))
                    .collect(Collectors.toList()));
    }

    private static List<Integer> processPoints(List<Point> rectangles, List<Point> points) {
        NavigableSet<Integer> uniqueSortedX = getSortedByX(points);
        NavigableSet<Integer> uniqueSortedY = getSortedByY(points);
        Map<Integer, Map<Integer, Point>> map = getMap(points);

        for (Point rectangle : rectangles) {
            for (int x : uniqueSortedX.subSet(0, true, rectangle.x, true)) {
                if (!map.containsKey(x))
                    continue;

                Set<Integer> ys = new HashSet<>(uniqueSortedY.subSet(0, true, rectangle.y, true));
                ys.retainAll(map.get(x).keySet());

                for (int y : ys)
                    if (map.get(x).containsKey(y))
                        map.get(x).get(y).count++;
            }
        }

        return points.stream()
                     .map(point -> point.count)
                     .collect(Collectors.toList());
    }

    private static NavigableSet<Integer> getSortedByX(List<Point> points) {
        NavigableSet<Integer> unique = new TreeSet<>();

        points.stream()
              .map(point -> point.x)
              .forEach(unique::add);

        return unique;
    }

    private static NavigableSet<Integer> getSortedByY(List<Point> points) {
        NavigableSet<Integer> unique = new TreeSet<>();

        points.stream()
              .map(point -> point.y)
              .forEach(unique::add);

        return unique;
    }

    private static Map<Integer, Map<Integer, Point>> getMap(List<Point> points) {
        Map<Integer, Map<Integer, Point>> map = new HashMap<>();
        points.forEach(point ->
                map.computeIfAbsent(point.x, x -> new HashMap<>()).put(point.y, point));
        return map;
    }

}

【讨论】:

  • 嗨,我在帖子中添加了一个额外的测试用例,当我使用该测试用例运行代码时,您的解决方案给我的结果为[22, 0, 37, 3, 0, 31, 0, 33, 37, 19, 50, 19, 19, 50, 3, 30, 9, 0, 50, 50, 3, 50, 50, 17, 8, 30, 3, 33, 3, 37, 5, 30, 43, 8, 50, 1, 45, 17, 34, 12, 2, 50, 5, 50, 14, 3, 17, 3, 50, 14],但预期的结果是[22, 50, 37, 3, 2, 31, 8, 33, 37, 19, 50, 19, 19, 50, 3, 30, 9, 50, 50, 50, 3, 50, 50, 17, 8, 30, 3, 33, 3, 37, 5, 30, 43, 8, 50, 1, 45, 17, 34, 12, 2, 50, 5, 50, 14, 3, 17, 3, 50, 14]。你能检查一下吗?
【解决方案4】:

我不是 Java 专家,这就是为什么我不能帮助你编写代码,但我会尝试解释一种算法来解决这个问题。

由于数据量大,您需要减少比较次数。这可以通过嵌套矩形的特殊层次结构来实现。

从问题顶部的示例中,矩形 [[1,2]、[2,3]、[1,5]] 您将在此类结构的顶层有两个分支:

[2,3] 和 [1,5]

在第二层 [2,3] 上会有一个子 [1,2]

[1,5] 不会有任何孩子

因此,当您检查每个点时,您将从顶部开始,并且只使用包含该点的分支。

在大量数据上,通过丢弃不必要的分支,您将获得更好的性能。

为了进一步提高性能,您需要在每个节点中存储子分支的深度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-22
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多