假设我有一个长度为L的数组A。我将获得n个区间(i,j),并且我必须增加A[i]和A[j]之间的所有值。哪种数据结构最适合进行这些操作?
这些区间事先已知。
假设我有一个长度为L的数组A。我将获得n个区间(i,j),并且我必须增加A[i]和A[j]之间的所有值。哪种数据结构最适合进行这些操作?
这些区间事先已知。
你可以获得O(N + M)的时间复杂度。保持一个额外的增量数组B,其大小与A相同,最初为空(填充为0)。如果你需要将区间(i,j)增加值k,则执行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]
将所有间隔分为开始和结束索引:s_i
,e_i
是第i个间隔的起始索引(包括s_i
)和结束索引(不包括e_i
)
将所有s_i
按照数组S排序
将所有e_i
按照数组E排序
将increment
设置为零,
开始线性扫描输入并将增量添加到每个人身上,
在每次循环中,如果下一个s_i
是当前index
,则增加increment
,
如果下一个e_i
是index
,则减少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>>m
,那么复杂度为O(n)
跳过元素后的复杂度:O(min(n, \sum length(I_i)))
,其中length(I_i)=e_i-s_i
\sum length(I_i)
不可能超过 n
(除非区间存在越界索引)。对 n
的依赖应该消失。 - Ted Hopp我可以想到三种主要方法:
方法1
这是最简单的方法,您只需保留数组不变,并对增量执行朴素操作。
方法2
这个方法有点复杂,但如果您计划经常增加,则更好。
将元素存储在二叉树中,使得中序遍历按顺序访问元素。每个节点(除了正常的左右子节点之外)还存储一个额外的int addOn
,它将在“查询此树中的任何节点”时添加。
对于查询元素,请在索引上执行正常的二进制搜索,同时将所有addOn
变量的值相加。将它们添加到您想要的节点的A[i]
中,这就是您的值。
addOns
。请注意,如果您将增量值加到一个节点的addOn
中,则不会为两个子节点更新它。每个增量的运行时间为O(log L)
,因为你只有在区间的第一个或最后一个元素在你的范围内时才必须“分支”。因此,您最多会分支2 log L
次,并且在元素中访问更多的常数因子。
方法3
使用区间树。
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.
}
}
}
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:
完成后,使用T中的区间与早期代码(按描述进行修改)一起使用。由于没有重叠,数组的任何元素都不会被增加多次。对于固定的区间集,这是一个常数时间算法,无论数组长度如何。
对于N个区间,分割过程可以通过保持按区间起始索引排序的T来设计运行时间接近O(N log N)。但是,如果成本在许多数组增量操作中分摊,那么这对于总体复杂度并不是非常重要。