我正在寻找一种从较大范围中删除范围列表的有效方法。 范围列表将包含更大的范围
例如:
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;
}
}
答案 0 :(得分:1)
通过将您的方法从“添加所有元素然后按范围删除”更改为“添加超出删除范围的元素”来引用@Bohemian想法
在最后一个范围的末尾添加所有元素
// 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解决方案中使用相同的想法状态来存储元素范围,而不是单个元素。
答案 1 :(得分:1)
我的想法是坚持索引而不是项值。好处是排除一个范围为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;
}
}
答案 2 :(得分:1)
要改善您的解决方案,您可以合并间隔列表,这是一个经典问题,您可以在此处找到代码:
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;
}
答案 3 :(得分:1)
虽然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
是外部范围的大小)的算法一起使用会出现问题。
答案 4 :(得分:1)
我不知道这样做的复杂性,但是认为使用Java-8解决会很有趣:
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
void main()
{
const char *array1[] = {"aa", "bb"};
const char *array2[] = {"cc", "dd", "ee"};
int arraySize1 = (sizeof(array1)/sizeof(char*));
int arraySize2 = (sizeof(array2)/sizeof(char*));
char **resultArr = (char **)malloc(sizeof(char *)*(arraySize1+arraySize2));
int i =0;
/* copy array1*/
for(i=0;i<arraySize1;i++)
{
resultArr[i] = malloc(strlen(array1[i]) +1);
strcpy(resultArr[i], array1[i]);
}
/*copy array2*/
for(i=0;i<arraySize2;i++)
{
resultArr[arraySize1+i] = malloc(strlen(array2[i])+1);
strcpy(resultArr[arraySize1+i], array2[i]);
}
/*print resulting array*/
for(i=0;i<arraySize1+arraySize2;i++)
{
printf("%s ", *(resultArr+i));
}
}
答案 5 :(得分:1)
我决定发布另一个答案,以显示具有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;
}
}