繁体   English   中英

从更大范围中删除范围列表中的元素的有效方法

[英]efficient way to remove elements from list of ranges from a bigger range

我正在寻找一种从更大范围中删除范围列表的有效方法。 范围列表将包含更大的范围

例如:

Bigger range: (0,10) 
List of Ranges:  [(2,7),(4,6),(6,8)]
expected result: {0,1,9,10}

我有一个实现如下,但它是O(n2)并占用额外的大小为O(n)的空间;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/***
* input -> (0,10) and {(2,7),(4,6),{6,8}}
 * output -> {0,1,9,10}
 ***/
public class RemoveRanges {

    public static class Range {
        int start;
        int end;

        public Range(int x, int y){
            this.start = x;
            this.end = y;

        }
    }

    public static void main(String[] args) {

        Range outer = new Range(0,10);
        Range r1 = new Range(2,7);
        Range r2 = new Range(4,6);
        Range r3 = new Range(6,8);
        List<Range> rangesToBeRemoved = new ArrayList<>();
        rangesToBeRemoved.add(r1);
        rangesToBeRemoved.add(r2);
        rangesToBeRemoved.add(r3);

        System.out.println(removeRanges(outer, rangesToBeRemoved));

    }

    public static Set<Integer> removeRanges(Range outer, List<Range> rangesToBeRemoved ) {

        Set<Integer> outerElements = new HashSet<>();

        for (int i = outer.start; i<=outer.end;i++ ){
            outerElements.add(i);
        }

        for (Range range : rangesToBeRemoved) {
            for (int j = range.start; j<=range.end; j++) {
                outerElements.remove(j);
            }
        }
        return outerElements;
    }
}

通过将“添加所有元素然后按范围删除”更改为“添加元素超出删除范围”,请参阅@Bohemian idea

  1. 对rangeToBeRemoved进行排序(通过range.start)
  2. 循环遍历范围并添加未按范围覆盖的元素
  3. 在最后一个范围结束后添加所有元素

     // assume rangesToBeRemoved has been sorted public static Set<Integer> addElementbyRemovedRanges(Range outer, List<Range> rangesToBeRemoved ) { Set<Integer> outerElements = new HashSet<Integer>(); // this variable record the last element that has handled and act like a borderline int borderElementIndex = outer.start-1; for (Range range : rangesToBeRemoved) { if (range.end <= borderElementIndex ) { // omit this range as it has been cover by previous range(s) continue; } // add range if there is gap between range if (range.start > borderElementIndex ) { addElements(outerElements, borderElementIndex + 1, range.start - 1); } // update borderline borderElementIndex = range.end; } // Add all element after the last range's end addElements(outerElements, borderElementIndex + 1, outer.end); return outerElements; } public static void addElements(Set<Integer> outerElements, int start, int end) { if (start > end) { return; } for (int i=start; i<=end; i++){ outerElements.add(i); } } 

对rangeToBeRemoved进行排序后,两个范围之间的关系为

  1. 完全在范围内(例如(2,7)和(4,6))
  2. 部分范围(例如(2,7)和(6,8))
  3. 不在范围内(例如(2,3)和(6,8)||(2,3)和(4,8))

对于案例1,忽略第二个范围。 对于案例2,将边界线更新为第二个范围的结束。 对于案例3,将间隙添加到元素列表并将边界线更新为第二个范围的结束。

上面的代码试图比较虚拟范围(outer.start-1,borderElementIndex)和rangesToBeRemoved(已排序)中的所有范围

重用你的例子:{(2,7),(4,6),(6,8)}。

  • 首先,将(-1,-1)与(2,7)和命中案例3进行比较,将间隙[0,1]添加到结果集中,并将borderElementIndex更改为7。
  • 接下来,比较(-1,7)和(4,6)并命中案例1,忽略它。
  • 然后,比较(-1,7)和(6,8)并命中案例2,将borderElementIndex更改为8。
  • 最后,将剩余差距[9,10]附加到结果集中。

为了进一步减少空间使用,您可以在@Danny_ds解决方案中使用相同的构思状态来存储元素范围而不是单个元素。

我的想法是坚持索引而不是项目值。 好处是排除一个范围的操作是O(1),因为我们不需要遍历数组的每个项目,而只需要更改一个索引值。 之后,我们应该通过数组索引来编译答案(有关如何构造结果的详细信息,请参阅printRange方法)。 至于产生的复杂性,解是O(n)+ O(m) ,其中n是外部范围大小, m是我们想要排除的范围数。 在使用解决方案的内存方面是O(n)因为我们需要使用额外的数组来存储n个大小的索引。

前提条件:我们要排除的所有范围都应按range.start值排序 如果它们未排序,则会为算法增加O(m * log(m))复杂度。

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Arrays;

/***
* input -> (0,10) and {(2,7),(4,6),{6,8}}
 * output -> {0,1,9,10}
 ***/
public class Main {

    public static class Range {
        int start;
        int end;

        public Range(int x, int y){
            this.start = x;
            this.end = y;

        }
    }

    public static void main(String[] args) {

        Range outer = new Range(0,10);
        Range r1 = new Range(2,7); //sorted ranges by range.start
        Range r2 = new Range(4,6);
        Range r3 = new Range(6,8);
        List<Range> rangesToBeRemoved = new ArrayList<>();
        rangesToBeRemoved.add(r1);
        rangesToBeRemoved.add(r2);
        rangesToBeRemoved.add(r3);


        printRange(outer, removeRanges(outer, rangesToBeRemoved));

    }

    public static void printRange(Range outer, int[] indexes)
    {
        int outerRangeSize = outer.end - outer.start + 2;
        int rangeShift = - (outer.start - 1);
        int current = 0;

        while (indexes[current] - rangeShift <= outer.end)
        {
            System.out.println(indexes[current] - rangeShift);
            current = indexes[current];
        }

    }

    public static int[] removeRanges(Range outer, List<Range> rangesToBeRemoved ) {
        int outerRangeSize = outer.end - outer.start + 2;
        int rangeShift = - (outer.start - 1);

        int[] outerElementsIndexes = new int[outerRangeSize];

        for (int i = 0; i<outerRangeSize;i++ ){
            outerElementsIndexes[i]=i+1; // construct indexes refereneces to the next indexes (one by one)
        }

        int currentIndex = 0; // point ot the first element in array
        int currentIndexNext = 1;

        for (Range range : rangesToBeRemoved) {
            if (currentIndex >= outerRangeSize) break;
            //int currentIndexNext = outerElementsIndexes[currentIndex];
            int nextIndexStart = range.start + rangeShift - 1; //calculate what index we should start from to exclude the range
            if (nextIndexStart < 0) nextIndexStart = 0;
            int nextIndexEnd = range.end + rangeShift + 1; // where we should jump to
            if (nextIndexEnd <= currentIndexNext) continue; // if we already skipped the range we're trying to exclude
            if (nextIndexStart <= currentIndexNext)
            {
              outerElementsIndexes[currentIndex] = nextIndexEnd; // case where we should extend the excluded range because it's intecepted with the last one we skipped

                currentIndexNext = nextIndexEnd;
            }
            else
            {
              outerElementsIndexes[nextIndexStart] = nextIndexEnd; // just exclude the range
              currentIndex = nextIndexStart;
              currentIndexNext = nextIndexEnd;
            }
        }
        return outerElementsIndexes;
    }
}

要改进您的解决方案,您可以合并间隔列表,这是一个经典问题,您可以在那里找到代码:
https://leetcode.com/problems/merge-intervals/discuss/21222/A-simple-Java-solution

然后你可以保持相同的代码,但它变成O(n)而不是O(n2),因为所有的间隔是不相交的,每个元素最多出现在一个输入间隔中

作为第二个改进,你可以检查当前值是否是间隔的左边,如果是,跳过该间隔:

public static Set<Integer> removeRanges(Range outer, List<Range> rangesToBeRemoved ) {

    HashMap<Integer, Integer> Ranges = new HashMap<>();
    for (Range range : rangesToBeRemoved) {
        Ranges.put(range.start, range.end);
    }

    Set<Integer> outerElements = new HashSet<>();
    for (int j = range.start; j<=range.end; j++) {
       if(Ranges.get(j))
       {
           int left=j, right=Ranges.get(j);
           j += right - left + 1; //skip this interval
       }
       else
       {
           outerElements.add(j);
       }
    }

    return outerElements;
}

虽然Bogemian的解决方案(评论)可能是最好的( “对范围进行排序,然后使用外部范围的循环输出,跳过范围” ),这是另一种方法:

Bigger range: (0,10) 
List of Ranges:  [(2,7),(4,6),(6,8)]

Result list: [(0,10)]

to remove (2,7) split the result list: [(0,1),(8,10)]
(4,6) -> no action
(6,8) -> [(0,1),(9,10)]

这可以在不对范围进行排序的情况下完成,但是每次我们必须在结果列表中查找位置。

两种解决方案在大范围内都表现良好(如果它们返回范围列表而不是包含所有值的列表)。

例如:

Bigger range: (0,4000000000) // 4 billion in uint32
List of Ranges:  [(200,1000000),(1000000000,2000000000)]

Result list: [(0,199),(1000001,999999999),(2000000001,4000000000)]

使用的空间很小,执行即时。 使用上述范围与使用O(n)空间的算法,其中n是外部范围的大小,将是有问题的。

我不知道这个的复杂性,但认为使用java-8解决它会很有趣:

Set<Integer> set = IntStream.concat(
            IntStream.range(outer.start, outer.end),
            rangesToBeRemoved.stream()
                    .reduce(
                            IntStream.empty(),
                            (stream, range) -> IntStream.concat(stream, IntStream.range(range.start, range.end)),
                            IntStream::concat)
                    .distinct())
            .boxed()
            .collect(Collectors.toMap(Function.identity(), x -> Boolean.TRUE, (x, y) -> null))
            .keySet();

我决定发布另一个答案,以显示具有O(1)+ O(m)复杂度的优化解决方案,其中m - 范围的数量,因此它不依赖于外部范围的大小。 但是,它需要O(n)内存。

它也不使用任何类,应该快速工作。

很高兴听到评论。

代码如下:

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Arrays;

/***
* input -> (0,10) and {(2,7),(4,6),{6,8}}
 * output -> {0,1,9,10}
 ***/
public class Main {

    public static class Range {
        int start;
        int end;

        public Range(int x, int y){
            this.start = x;
            this.end = y;

        }
    }

    public static void main(String[] args) {

        Range outer = new Range(0,10);
        Range r1 = new Range(2,7); //sorted ranges by range.start
        Range r2 = new Range(4,6);
        Range r3 = new Range(6,8);
        List<Range> rangesToBeRemoved = new ArrayList<>();
        rangesToBeRemoved.add(r1);
        rangesToBeRemoved.add(r2);
        rangesToBeRemoved.add(r3);


        printRange(outer, removeRanges(outer, rangesToBeRemoved));

    }

    public static void printRange(Range outer, int[] indexes)
    {
        int outerRangeSize = outer.end - outer.start + 2;
        int rangeShift = - (outer.start - 1);
        int current = 0;
        int currentNext = ((indexes[current] > 0) ? indexes[current] : current + 1);

        while (currentNext - rangeShift <= outer.end)
        {
            System.out.println(currentNext - rangeShift);
            current = currentNext;
            currentNext = ((indexes[current] > 0) ? indexes[current] : current + 1);
        }

    }

    public static int[] removeRanges(Range outer, List<Range> rangesToBeRemoved ) {
        int outerRangeSize = outer.end - outer.start + 2;
        int rangeShift = - (outer.start - 1);

        int[] outerElementsIndexes = new int[outerRangeSize];

        int currentIndex = 0; // point ot the first element in array
        int currentIndexNext = 1;

        for (Range range : rangesToBeRemoved) {
            if (currentIndex >= outerRangeSize) break;
            int nextIndexStart = range.start + rangeShift - 1; //calculate what index we should start from to exclude the range
            if (nextIndexStart < 0) nextIndexStart = 0;
            int nextIndexEnd = range.end + rangeShift + 1; // where we should jump to
            if (nextIndexEnd <= currentIndexNext) continue; // if we already skipped the range we're trying to exclude
            if (nextIndexStart <= currentIndexNext)
            {
              outerElementsIndexes[currentIndex] = nextIndexEnd; // case where we should extend the excluded range because it's intecepted with the last one we skipped

                currentIndexNext = nextIndexEnd;
            }
            else
            {
              outerElementsIndexes[nextIndexStart] = nextIndexEnd; // just exclude the range
              currentIndex = nextIndexStart;
              currentIndexNext = nextIndexEnd;
            }
        }
        return outerElementsIndexes;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM