一.差分
差分数组是解决某类区间的有力工具,当我们面临需要频繁对原数组的某个区间内的元素进行增减操作时,直接操作会导致时间复杂度过高。这时,差分数组就能发挥巨大作用。
基本概念
差分数组d[ ]用于表示相邻元素之间的差值,对于任意的数组a[ ],其差分数组d[ ]可以表示为:
d[i]=a[i]-a[i-1];
而对于数组的第一个元素,我们可以定义d[0]=a[0];
构建差分数组
假设我们有一个原始数组a[ ],我们可以按照以下方式构建差分数组d[ ];
int[] a={5,3,6,4}; //原始数组
int[] d=new int[a.length];
//构建差分数组
d[0]=a[0];//第一个元素不变
for(int i=1;i<a.length;i++){
d[i]=a[i]-a[i-1];
}
原始数组的第一个元素不变,后面的每一个元素减前一个元素,所得到的数组即为差分数组
区间增减操作
差分数组最大的优势在于处理区间操作,如果我们想对a[l~r]区间内所有元素增加一个值x,我们只需要对差分数组做以下操作:
d[l]+=x;
if(r+1<a.length){
d[r+1]-=x;
}
这样,当我们通过差分数组重新计算原数组时,区间[l,r]内所有元素都增加了x,而其他元素保持不变。(简单来说就是若要使区间内的所有元素都增加一个数x,则区间内第一个数(l)增加x,区间后的第一个数(r+1)减去x)
还原原数组
通过对差分数组的的一系列操作,我们可以通过以下代码来还原出修改后的原数组:
a[0]=d[0];//第一个元素依然是差分数组的第一个元素
for(int i=1;i<a.length;i++){
a[i]=d[i]+a[i-1];
}
修改后的原数组,第一个元素等于差分数组中的第一个元素,之后的每一个元素等于修改后原数组的前一个元素a[i-1]加上差分数组中对应的元素(d[i])
示例:
//nums数组是之前已经定义的一个数组(原数组)
int diff=new int[nums.length]; //定义差分数组
//构造差分数组
diff[0]=num[0];
for(int i=1;i<nums.length;i++){
diff[i]=nums[i]-nums[i-1];
}
//对原数组进行加操作
int left=1; //区间左端点:索引1
int right=4; //区间右端点:索引4
int value=2; //区间内所有元素加的数值
diff[left]+=value;
if(right+1<nums.length){
diff[right+1]-=value;
}
//根据差分数组回复原数组
int [] res=new int[nums.length];
res[0]=diff[0];
for(int i=1;i<diff.length;i++){
res[i]=res[i-1]+diff[i];
}
二.前缀和
前缀和是一种常见且实用的算法技巧,它主要用于处理数组区间求和问题。简而言之,前缀和就是一个数组的某个下标之前的所有元素之和。
应用场景:
前缀和算法适用于数组不经常变动而频繁查询某个区间内元素的总和的场景。如处理多个查询请求,每个请求要求输出数组中某个区间的元素和
和差分算法的区别
前缀和算法和差分算法从某种程度上来说互为逆运算,其在代码上也十分相似,但主要在于目的不同;前缀和算法是直接基于原数组,算出某个区间的所有元素之和。而差分算法是在原数组上进行操作,最后得到一个新的数组。
前缀和的计算方法
int[] arr={a1,a2,a3,...,an};
int[] sum=new int[n+1];//前缀和数组
sum[0]=0;//前缀和数组第一个直接赋值为0
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+arr[i-1];
}
使用前缀和查询区间和
要查询数组arr从下标L到下标R的区间和(即[L,R]),可以用两个前缀和相减的方式实现:
int sum_L_to_R=sum[R+1]-sum[L];
快速生成前缀和数组方法
在算法题目中,我们会遇到不需要原数组,只需要前缀和数组的情况。我们可以使用以下代码:
//正常读取用户输入的数字
for(int i=1;i<=n;i++)
s[i]=scanner.nextInt();
//生成前缀和数组
for(int i=1;i<=n;i++)
s[i]+=s[i-1];
注意事项
·前缀和数组大小是原数组大小加一,以便处理从数组首元素开始的区间查询
·如果数组发生更新,则需要重新计算前缀和数组,或者进行差分处理
·对于多位数组,可以使用类似的方法计算多维前缀和
示例:
public class PrefixSum2D {
private int[][] prefixSum;
// 构建二维前缀和
public PrefixSum2D(int[][] matrix) {
int row = matrix.length;
int col = matrix[0].length;
prefixSum = new int[row + 1][col + 1];
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
prefixSum[i][j] = matrix[i - 1][j - 1] + prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1];
}
}
}
// 查询子矩阵的和
public int querySubmatrixSum(int row1, int col1, int row2, int col2) {
return prefixSum[row2 + 1][col2 + 1] - prefixSum[row1][col2 + 1] - prefixSum[row2 + 1][col1] + prefixSum[row1][col1];
}
// 打印二维前缀和数组,用于验证
public void printPrefixSum() {
for (int i = 1; i < prefixSum.length; i++) {
for (int j = 1; j < prefixSum[0].length; j++) {
System.out.print(prefixSum[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
PrefixSum2D prefixSum2D = new PrefixSum2D(matrix);
// 查询从(1, 1)到(2, 2)子矩阵的和
int submatrixSum = prefixSum2D.querySubmatrixSum(1, 1, 2, 2);
System.out.println("Sum of submatrix from (1,1) to (2,2): " + submatrixSum);
// 验证前缀和数组是否正确
System.out.println("Prefix Sum Array:");
prefixSum2D.printPrefixSum();
}
}