1.前缀和
题目:有nums=[1, 3 ,7, 5, 2],求[2,4]下标数字和,[0,3]下标数字和...?
一般的,[L,R]区间和,q次询问
暴力方法
每次询问都去遍历一遍数组(代码省略)
时间复杂度O(q*n)
前缀和方法
nums : 1 3 7 5 2
sum : 1 4 11 16 18
sum前缀和数组的求法:
-
sum[i] = sum[i-1] + arr[i] (i>0)
-
sum[i] = nums[0] (i==0)
如果想要求[2,4]下标数字和,即sum[4] - sum[2-1] = 18 - 4 = 14。
总结,[L, R]区间和求法:
-
[L,R]和 = sum[R] - sum[L-1] (L>0)
-
[L,R]和 = sum[R](L==0)
代码:
/**
* 前缀和主要用于求,arr[L,R] 区间的和
* 时间复杂度O(n)
*/
public class PrevSum {
public static void main(String[] args) {
new PrevSum().prevSumOne();
}
// 一维的前缀和
public void prevSumOne() {
int[] nums = {1, 3, 7, 5, 2};
int[] sum = new int[nums.length]; // 前缀和数组
sum[0] = nums[0];
for (int i = 1; i < nums.length; i++) { // 求前缀和数组,sum[i] == arr[0,i]的区间和
sum[i] = sum[i - 1] + nums[i];
}
int res1 = getSum(sum, 2, 4);
int res2 = getSum(sum, 0, 3);
int res3 = getSum(sum, 3, 4);
System.out.println(res1 + " " + res2 + " " + res3);
}
private int getSum(int[] sum, int left, int right) {
if (left > 0) {
return sum[right] - sum[left - 1];
} else {
return sum[right];
}
}
}
2. 一维差分
题目:有nums=[1, 3 ,7, 5, 2],求[2,4]下标区间加5,[1,3]下标区间加2,最终nuns数组的状态? 一般的,进行若干次[L,R]区间加value,求数组最终状态
暴力方法
每次操作都去遍历数组进行加value(代码省略)
时间复杂度O(n*m)
差分方法
nums : 1 3 7 5 2
diff : 1 2 4 -2 -3 (差分数组就是相邻两个数的差)
sumd: 1 3 7 5 2
我们发现对差分数组进行前缀和能还原原数组,前缀和差分互为逆运算的!
但这个题用不到上面的性质,我们要使用差分标记,即进行多次区间操作的时候并不马上进行加value操作,而是进行标记,最终一起进行加减。
例如[1,3]下标区间加2,即进行标记:diff[1] + 2, diff[4] + 2
一般的差分标记:
-
[L,R] + value == diff[L] + value , diff[R + 1] - value (R < nums.length - 1)
-
[L,R] + value == diff[L] + value (R == nums.length - 1,多开一个空间就不同判断了)
代码:
/**
* 差分 时间复杂度 O(m + n)
* 主要用于求 arr[L,R] 区间增加或减一个值,最终arr数组的状态
*/
public class Difference {
public static void main(String[] args) {
new Difference().differenceOne();
}
// 一维差分
public void differenceOne() {
int[] nums = {1, 3, 7, 5, 2}; // 例如[2, 4]+5, [1, 3]+2, [0, 2]-3
// 差分标记数组,初始化为0就行(可以不用初始化为相邻两个数的差,最后加上原数组的数就行)
int[] diff = new int[nums.length];
add(diff, 2, 4, 5);
add(diff, 1, 3, 2);
add(diff, 0, 2, -3);
// 通过差分标记数组,还原操作后的数组
for (int i = 1; i < nums.length; i++) { // 1.先让差分标记数组做前缀和
diff[i] = diff[i - 1] + diff[i];
}
for (int i = 0; i < nums.length; i++) { // 2.此时的diff就是最终操作的区间和值,最后加上原数组的数就行
nums[i] += diff[i];
}
System.out.println(Arrays.toString(nums)); // [-2, 2, 11, 12, 7]
}
private void add(int[] diff, int left, int right, int value) {
// 差分标记数组的公式: [L,R]+v == diff[L]+v, diff[R+1]-V
diff[left] += value;
if(right + 1 < diff.length) {
diff[right + 1] -= value;
}
}
}