[英]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
在最后一个范围结束后添加所有元素
// 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,将边界线更新为第二个范围的结束。 对于案例3,将间隙添加到元素列表并将边界线更新为第二个范围的结束。
上面的代码试图比较虚拟范围(outer.start-1,borderElementIndex)和rangesToBeRemoved(已排序)中的所有范围
重用你的例子:{(2,7),(4,6),(6,8)}。
为了进一步减少空间使用,您可以在@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.