【问题标题】:How to increment all values in an array interval by a given amount如何将数组间隔中的所有值增加给定的数量
【发布时间】:2013-08-26 20:22:49
【问题描述】:

假设我有一个长度为 L 的数组 A。我将得到 n 个区间 (i,j),我必须递增 A[i] 和 A[j] 之间的所有值。哪种数据结构最适合给定的操作?
间隔是预先知道的。

【问题讨论】:

  • 视情况而定。您还计划支持哪些其他操作?
  • 我需要数组 A 的最终值。
  • 所以,您是说您希望能够存储一个元素数组,并能够 1) 将某个范围内的所有元素递增该值,以及 2) 查询数组中的任何元素?
  • 循环遍历间隔;对于每个间隔,使用 for 循环递增间隔内的每个 Array 条目。如果数组访问过于“昂贵”,则从所有间隔中创建一个新的计数数组并仅访问 A 数组一次。
  • 间隔可以重叠吗?如果是这样,您是否要在重叠中多次递增?

标签: algorithm data-structures range-query


【解决方案1】:

解决单个区间的问题。然后遍历所有区间并为每个区间应用单区间解。最好的数据结构取决于语言。这是一个 Java 示例:

public class Interval {
    int i;
    int j;
}

public void increment(int[] array, Interval interval) {
    for (int i = interval.i; i < interval.j; ++i) {
        ++array[i];
    }
}

public void increment(int[] array, Interval[] intervals) {
    for (Interval interval : intervals) {
        increment(array, interval);
    }
}

如果您想减少代码量,显然您可以将一个循环嵌套在另一个循环中。但是,单间隔方法本身可能很有用。

编辑

如果事先知道间隔,那么您可以稍微改进一下。您可以修改Interval 结构以保持增量(默认为1)。然后对区间集合S进行如下预处理:

  1. 将第二组区间 T 初始化为空集
  2. 对于S中的每个区间I:如果I不与T中的任何区间重叠,则添加IT;否则:
  3. 对于 T 中与 I 重叠的每个区间 J,从 T 中删除 J em>,形成新的区间 K1...Kn from IJ 使得没有重叠(n 可以从 1 到 3),并添加 K1...KnT

完成后,将 T 中的间隔与之前的代码(如所述修改)一起使用。由于没有重叠,因此数组的任何元素都不会增加一次以上。对于一组固定的间隔,这是一个恒定时间算法,与数组长度无关。

对于 N 个间隔,通过保持 T 按间隔开始索引排序,可以将拆分过程设计为在接近 O(N log N) 的情况下运行。但如果成本在许多数组增量操作中分摊,这对整体复杂性来说并不是那么重要。

【讨论】:

  • 我希望复杂度远小于 O(LN),其中 L 是数组的长度,N 是间隔数
  • @user2094963 - 复杂度为 O(MN),其中 M 是区间的(平均)长度,N 是区间数。底层数组的长度不会以任何方式影响操作的数量。 也许做得更好的唯一方法是预处理间隔,在重叠时拆分,并跟踪每个间隔的增量。即便如此,复杂性仍然是 O(MN),只是 M 减少和 N 增加。(这将如何影响乘积 MN 取决于您正在处理的重叠程度。)加上预处理将有其自身的复杂性。
  • 为什么不做一个增量数组呢?那不是 O(L+N) 吗?
  • @Jim - 增量数组正是我所建议的。但是,复杂度不是 O(L+N)。考虑:如果一个区间是 M 长(M = j - i),那么需要 M 个增量操作;没有办法解决这个问题(除非有一些硬件支持并行处理,但我假设我们使用的是SISD 机器)。对于 N 个区间,复杂度将因此为 O(MN)。我根本看不到 L(数组的长度)是如何进入方程的。
  • @Ted 如果你将间隔组合成两个一组,然后组合组合,我认为你可以避免一些增量并且可能达到 O(NlnM)。
【解决方案2】:

我能想到的主要方法有以下三种:

方法 1

这是最简单的一种,你只需保持数组不变,然后做一些简单的事情来增加。

  • 优点:查询是固定时间
  • 缺点:增量可以是线性时间(如果 L 很大,因此会很慢)

方法2

这个有点复杂,但如果你打算增加很多,那就更好了。

将元素存储在二叉树中,以便中序遍历按顺序访问元素。每个节点(除了正常的左右子子节点外)还额外存储了一个int addOn,即“查询此树中的任何节点时添加我”。

对于查询元素,对索引执行正常的二进制搜索以查找元素,同时将addOn 变量的所有值相加。将这些添加到您想要的节点处的A[i],这就是您的价值。

对于增量,向下遍历树,根据需要更新所有这些新的addOns。请注意,如果您将增量值添加到一个节点的addOn,您不会为两个子节点更新它。然后每个增量的运行时间为O(log L),因为您唯一需要“分支”到子代的时间是区间中的第一个或最后一个元素在您的范围内时。因此,您最多分支2 log L 次,并在元素中更多地访问常数因子。

  • 优点:增量现在是 O(log L),所以如果你增加一吨,现在事情会比以前快得多。
  • 缺点:查询需要更长的时间(也是 O(log L)),并且实现起来要复杂得多。

方法 3

使用interval tree

  • 优点:就像方法 2 一样,这种方法可能比简单方法快得多
  • 缺点:如果您事先不知道间隔将是多少,则不可行。
    实施起来也很棘手

【讨论】:

  • 这个问题可以用普通的段树来解决吗?如果是,如何?
  • 可能。我只是先想到这些。
  • 我觉得用线段树解决这个问题与方法2没有太大区别;将每个 addOn 视为对应于该子树中元素的段/间隔。
【解决方案3】:

将所有区间划分为开始和结束索引:s_i,e_i 用于第 i 个区间,从包含 s_i 开始到不包括 e_i 结束

将所有s_i-s 排序为数组S 将所有e_i-s 排序为数组E

increment 设置为零 开始对输入进行线性扫描并向每个人添加增量, 在每个循环中,如果下一个s_i 是当前index,则递增increment 如果下一个e_iindex,则递减increment

inc=0
s=<PriorityQueue of interval startindexes>
e=<PriorityQueue of interval endindexes>
for(i=0;i<n;i++){
  if( inc == 0 ){
    // skip adding zeros
    i=min(s.peek(),e.peek())
  }
  while( s.peek() == i ) {
    s.pop();
    inc++;
  }
  while( e.peek() == i ) {
    e.pop();
    inc--;
  }
  a[i]+=inc;
}

复杂度(不跳过非增量元素):O(n+m*log(m)) // m 是区间数 如果n&gt;&gt;m 则为O(n)

跳过元素时的复杂性:O( min( n , \sum length(I_i) ) ),其中length(I_i)=e_i-s_i

【讨论】:

  • 如果你事先不知道间隔是多少怎么办?
  • 如果我正确解释了这个问题,间隔将作为输入或输入块给出
  • 我想我在想他只会在增量操作中获得间隔的情况,并假设他必须在查询到来时回答它们(即不能一次全部完成)
  • 那是另一种情况......也许应该澄清这个问题
  • 不是每个开始排序都有 O(NlnN) 吗?
【解决方案4】:

你可以得到 O(N + M)。保持一个与 A 相同大小的额外增量数组 B 最初为空(填充为 0)。如果您需要用值 k 增加范围 (i, j),则执行 B[i] += k 和 B[j + 1] -= k

现在在 B 中进行部分求和转换,考虑到您从 0 开始索引:

for (int i = 1; i < N; ++i) B[i] += B[i - 1];

现在 A 的最终值为 A[i] + B[i]

【讨论】:

  • 很棒的解决方案!只有一个建议,在创建 B 数组时,如果 j+1
  • 真棒....bdw 这个解决方案背后的直觉是什么,我们也有这个方法的正式名称。
  • 对于寻找此方法名称的未来读者,它被称为Difference Array
【解决方案5】:

Adrian Budau建议的O(M+N)算法的可能实现

import java.util.Scanner;

class Interval{
    int i;
    int j;
}

public class IncrementArray {
    public static void main(String[] args) {
        int k= 5;                                   // increase array elements by this value
        Scanner sc = new Scanner(System.in);
        int intervalNo = sc.nextInt();                // specify no of intervals
        Interval[] interval = new Interval[intervalNo];           // array containing ranges/intervals
        System.out.println(">"+sc.nextLine()+"<");
        for(int i=0;i<intervalNo;i++)
        {
            interval[i]= new Interval();
            String s = sc.nextLine();                             // specify i and j separated by comma in one line for an interval.
            String[] s1 = s.split(" ");
            interval[i].i= Integer.parseInt(s1[0]);
            interval[i].j= Integer.parseInt(s1[1]);
        }
        int[] arr = new int[10];           // array whose values need to be incremented.
        for(int i=0;i<arr.length;++i)
            arr[i]=i+1;                    // initialising array.
        int[] temp = new int[10];
        Interval run=interval[0]; int i;
        for(i=0;i<intervalNo;i++,run=interval[i<intervalNo?i:0] )  // i<intervalNo?i:0 is used just to avoid arrayBound Exceptions at last iteration.
        {
            temp[run.i]+=k;
            if(run.j+1<10)                                          // incrementing temp within array bounds.
            temp[run.j +1]-=k;
        }
        for (i = 1; i < 10; ++i) 
            temp[i] += temp[i - 1];

        for(i=0, run=interval[i];i<10;i++)
            {
                arr[i]+= temp[i];
                System.out.print(" "+arr[i]);                     // printing results.
            }


    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-12
    • 2021-10-08
    • 1970-01-01
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多