前缀和数组
| 序号 | 题目 | 完成 |
|---|---|---|
| 1 | 303. 区域和检索 - 数组不可变 | ✅ |
| 2 | 304. 二维区域和检索 - 矩阵不可变 | ✅ |
| 3 | 剑指 Offer II 013. 二维子矩阵的和 | ✅ |
| 4 | 1314. 矩阵区域和 | ✅ |
在不改变原数组的情况下,计算区间和。 涉及到求区间和的问题,就要想到用前缀和解决。
一维数组
class NumArray {
int[] preSum;
public NumArray(int[] nums) {
int n = nums.length;
preSum = new int[n];
int sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
preSum[i] = sum;
}
}
public int sumRange(int left, int right) {
if (left == 0) {
return preSum[right];
} else {
return preSum[right] - preSum[left - 1];
}
}
}
二维数组
相同问题: 剑指 Offer II 013. 二维子矩阵的和
class NumMatrix {
int[][] preSum;
public NumMatrix(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int sum = 0;
preSum = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
preSum[i][j] = preSum[i][j - 1] + preSum[i - 1][j] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1];
}
}
}
public int sumRegion(int x1, int y1, int x2, int y2) { // 目标矩阵之和由四个相邻矩阵运算获得
return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1];
}
}
可以转换为:求[i-k, j-k]到[i+k, j+k]两个节点之间的元素和。
class Solution {
public int[][] matrixBlockSum(int[][] mat, int k) {
int m = mat.length;
int n = mat[0].length;
int[][] preSum = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 先得到前缀和数组
preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + mat[i - 1][j - 1] - preSum[i - 1][j - 1];
}
}
int[][] target = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int x1 = i > k ? i - k : 0;
int x2 = i + k < m ? i + k : m - 1;
int y1 = j > k ? j - k : 0;
int y2 = j + k < n ? j + k : n - 1;
target[i][j] = get(preSum, x1, y1, x2, y2);
}
}
return target;
}
public int get(int[][] mat, int x1, int y1, int x2, int y2) {
return mat[x2 + 1][y2 + 1] - mat[x2 + 1][y1] - mat[x1][y2 + 1] + mat[x1][y1];
}
}
差分数组
对区间内的元素进行频繁的计算。
| 序号 | 题目 | 完成 |
|---|---|---|
| 1094. 拼车 | ✅ | |
| 1109. 航班预订统计 | ✅ | |
| 370. 区间加法 | ✅ | |
| 1674. 使数组互补的最少操作次数 | ✅ | |
| 798. 得分最高的最小轮调 | ||
| 1943. 描述绘画结果 |
class Solution {
public int[] getModifiedArray(int length, int[][] updates) {
int[] ans = new int[length];
int[] diff = new int[length];
for (int[] update : updates) {
int x = update[0];
int y = update[1] + 1;
int val = update[2];
diff[x] += val;
if (y <= length - 1) {
diff[y] -= val;
}
}
ans[0] = diff[0];
for (int i = 1; i < length; i++) {
ans[i] = ans[i - 1] + diff[i];
}
return ans;
}
}
class Solution {
public boolean carPooling(int[][] trips, int capacity) {
int max = 0;
for (int[] trip : trips) {
max = Math.max(max, trip[2]);
}
int len = max + 1;
int[] nums = new int[len];
// 差分数组初始值都为0,因为容量一开始就确定了,每一个元素值都相等,差分值自然也相等
int[] diff = new int[len];
for (int[] trip : trips) {
int x = trip[1];
// 到站就下车了,所以y不需要包含
int y = trip[2];
// 此时上车了val名乘客,对[x,y)范围内的容量减去val
diff[x] += trip[0];
diff[y] -= trip[0];
}
// 得到原数组
for (int i = 0; i < len; i++) {
if (i == 0) {
nums[0] = diff[0];
} else {
nums[i] = nums[i - 1] + diff[i];
}
// 出现容量不够的情况,立刻返回false
if (nums[i] > capacity) {
return false;
}
}
return true;
}
}
class Solution {
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] ans = new int[n];
int[] diff = new int[n];
for (int[] booking : bookings) {
int x = booking[0] - 1;
int y = booking[1];
int val = booking[2];
diff[x] += val;
if (y < n) {
diff[y] -= val;
}
}
ans[0] = diff[0];
for (int i = 1; i < n; i++) {
ans[i] = diff[i] + ans[i - 1];
}
return ans;
}
}
题目的要求是数组对的和都相等,我们事先并不知道最少操作数的数组对的和是多少,所以我们只能把所有可能和带来的操作数都计算一遍。
首先
因为所有的元素都是[1, limit],那我们可以知道,数组对的和的范围必然是[2, limit * 2]
接下来,我们就来计算数组对的和分别为[2, limit*2]所需要的操作数。
我们定义一个ans数组,该数组保存了数组和为index的所需的操作数,比如idx=2的位置就是,数组对和为2时,所有数组对的最小操作数。
第二步
我们来看一个数组对需要操作几次才能达到我们期望的和。
显然,对于两个数字:
- 最少操作零次(和已经目标一样,不需要替换了)。
- 经过一次操作,得到的和肯定在
[smaller + 1, bigger + limit]之间。 - 最多操作两次(两个数字都要换)
那么可以得到:
- 在[2, min]这个区间,arr[i] += 2;
- 在[min + 1, min + max]区间,arr[i] += 1;
- 在min + max上,arr[i] += 0;
- 在[min + max + 1, max + limit]区间上,arr[i] += 1;
- 在[max + limit + 1, limit + limit]区间上,arr[i] += 2;
我们知道区间和频繁修改建议用差分数组,差分数组与原数组的关系:
如果需要操作
[i,j]区间的值都加val,那么diff[i] += val;diff[j+1] -= val;
最后
可以得到对差分数组的操作公式:
- [2, min] :
diff[2] +=2; diff[min+1] -=2; - [min + 1, min + max) :
diff[min+1] +=1; diff[min+max] -=1; - 等于 min + max:不变
- [min + max + 1, max + limit] :
diff[min + max + 1] += 1; diff[max + limit + 1] -= 1; - [max + limit + 1, limit * 2] :
diff[max + limit + 1] +=2; diff[limit * 2 + 1] -=2;
class Solution {
public int minMoves(int[] nums, int limit) {
int n = nums.length;
int[] diff = new int[2 * limit + 2];
// 遍历每一个数组对,因为题目是需要计算总的操作数,所以需要遍历所有的数组对
for (int i = 0; i < n / 2; i++) {
// 得到min和max
int max = Math.max(nums[i], nums[n - i - 1]);
int min = Math.min(nums[i], nums[n - i - 1]);
if (min == 1) {
diff[2] += 1;
} else {
diff[2] += 2;
diff[min + 1] -= 1;
}
diff[min + max] -= 1;
diff[min + max + 1] += 1;
diff[max + limit + 1] += 1;
}
int ans = Integer.MAX_VALUE;
int now = 0;
// ans的取值范围[2, limit * 2]
// ans[i] = ans[i-1] + diff[i]
for (int i = 2; i < 2 * limit + 1; i++) {
now += diff[i];
ans = Math.min(ans, now);
}
return ans;
}
}
解题思路
定义scores[k],表示进行k次轮调之后的得分总数,那对于nums里的每一个元素,得到其需要得分加1的k的区间,再来操作scores就可以了。
条件:对于nums[i]来说,i >= nums[i]的情况下,需要加1。
比如:[2,3,1,4,0]
当 i=2 时,nums[i] = 1,i > nums[i]
- k=0,[2,3,1,4,0],移动到2的位置,+1
- k=1,[3,1,4,0,2],移动到1的位置,+1
- k=2,[1,4,0,2,3],移动到0的位置
- k=3,[4,0,2,3,1],移动到4的位置,+1
- k=4,[0,2,3,1,4],移动到3的位置,+1
当 i=3 时,nums[i] = 4,i < nums[i]
- k=0,[2,3,1,4,0],移动到3的位置
- k=1,[3,1,4,0,2],移动到2的位置
- k=2,[1,4,0,2,3],移动到1的位置
- k=3,[4,0,2,3,1],移动到0的位置
- k=4,[0,2,3,1,4],移动到4的位置,+1
经过k次调度后,i的位置为:(i + n - k) % n
怎么得到的?
因为调度是往左移k位,正常是 i - k,考虑到越界,我们不如在数组前面加一个[2,3,1,4,0,2,3,1,4,0]这样就不会越界,加了新的一段后,i的位置变为i+n
i变化的规律是:先变小后变大
nums[i]一直不变。
对每一个元素进行分析(i >= nums[i]则加1)
(1)当i < nums[i]时:k在[0,i-x]和[i+1,n-1] 加1。
(2)i == nums[i]时,加1;
(3)当i > nums[i]时:k在[i+1, i-x+n]加1。
class Solution {
public int bestRotation(int[] nums) {
int n = nums.length;
int[] diffs = new int[n];
// 当i上的元素被调度到[nums[i], n-1]的位置上时才会被计分
// i>x时, [i+1, i-x+n] 加1
// i<=x时,[0,i-x]和[i+1,n-1] 加1
for (int i = 0; i < n; k++) {
int low = (i + 1) % n;
int high = (i - nums[i] + n + 1) % n;
// 对[low, high-1]内的得分+1
diffs[low]++;
diffs[high]--;
if (low >= high) {
diffs[0]++;
}
}
// diff是对k的得分的差分数组
int bestIndex = 0;
int maxScore = 0;
int score = 0;
// 得到最大值
for (int k = 0; k < n; k++) {
score += diffs[k];
if (score > maxScore) {
bestIndex = k;
maxScore = score;
}
}
return bestIndex;
}
}