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

时间:2018-08-10 05:49:31

标签: java algorithm data-structures

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

例如:

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;
    }
}

6 个答案:

答案 0 :(得分:1)

通过将您的方法从“添加所有元素然后按范围删除”更改为“添加超出删除范围的元素”来引用@Bohemian想法

  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,7)与(6,8)进行比较,并找到情况2,将borderElementIndex更改为8。
  • 最后,将剩余的间隙[9,10]附加到结果集中。

要进一步减少空间使用量,可以在@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;
    }
}