子矩阵元素加 1
问题描述
给你一个正整数 n ,表示最初有一个 n x n 、下标从 0 开始的整数矩阵 mat ,矩阵中填满了 0 。
另给你一个二维整数数组 query 。针对每个查询 query[i] = [row1i, col1i, row2i, col2i] ,请你执行下述操作:
找出 左上角 为 (row1i, col1i) 且 右下角 为 (row2i, col2i) 的子矩阵,将子矩阵中的 每个元素 加 1 。也就是给所有满足 row1i <= x <= row2i 和 col1i <= y <= col2i 的 mat[x][y] 加 1 。
返回执行完所有操作后得到的矩阵 mat 。
(真题链接:子矩阵元素加 1)
解题思路
如果之前了解过二维数组差分的朋友们很容易就可以解决这个问题,所以这里着重给没了解过的朋友们介绍一下。
来自deepseek:
二维差分数组是一种通过O(1)时间 记录子矩阵更新、再通过二维前缀和还原的技术。具体来说,要对原矩阵中某个矩形区域统一加一个值,只需在差分数组的四个角做标记:起点加该值、右侧和下方的边界外减该值、右下角外再加回该值;最后对差分数组求二维前缀和,就能得到每个位置被累计更新的总量,从而高效处理大量批量区域更新问题。
简单来说:
就像差分数组一样,我们只需要在一个二维数组的[0, 0]位置放上一个 1,就可以实现整个二维数组的每一个位置都是1。
下面笔者将以可视化的形式带读者更清晰的认识二维差分数组:
二维差分数组原理图示
原矩阵:
1 2 4 3
5 3 2 1
1 4 2 5
差分矩阵(初始化为0):
0 0 0 0
0 0 0 0
0 0 0 0
1. 单点更新(以左上角(1,1)到右下角(2,2)矩形区域+3为例)
差分矩阵操作点(假设行列从1开始计数):
(1,1) += 3
(1,3) -= 3
(3,1) -= 3
(3,3) += 3
更新后差分矩阵:
+3 0 -3 0
0 0 0 0
-3 0 +3 0
2. 前缀和还原矩阵
通过二维前缀和计算还原修改后的原矩阵:
第一行递推:
1→4→1→1 (原值1+3=4;4+(-3)=1;1+0=1)
其他行列类似递推...
最终原矩阵:
4 5 1 3
5 3 2 1
-2 1 5 5
可视化辅助图
操作区域示意图:
(1,1)───────(1,2)
│ +3 │
│ │
(2,1)───────(2,2)
差分矩阵影响点:
● (1,1) +c ○ (1,3) -c
○ (3,1) -c ● (3,3) +c
代码实现参考
一维差分:a[i] = diff[i] + a[i-1]
二维差分:a[i][j] = diff[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1]
有了上面关于二维差分数组的基本理解,我们再来看题目就很容易了。我们只需要在每一个标记的点的位置将他 +1并平衡数组,等全部的位置标记完成后再进行二维差分数组的复原即可。
代码实现
cpp
class Solution {
public:
vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {
vector<vector<int>> ret(n, vector<int>(n, 0));
int x1, y1, x2, y2;
for(int i = 0; i < queries.size(); i++)
{
x1 = queries[i][0],y1 = queries[i][1],x2 = queries[i][2],y2 = queries[i][3];
ret[x1][y1] += 1;
if(x2 < n - 1)ret[x2 + 1][y1] += -1;
if(y2 < n - 1)ret[x1][y2 + 1] += -1;
if(x2 < n - 1 && y2 < n - 1)ret[x2 + 1][y2 + 1] += 1;
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i > 0) ret[i][j] += ret[i-1][j];
if (j > 0) ret[i][j] += ret[i][j-1];
if (i > 0 && j > 0) ret[i][j] -= ret[i-1][j-1];
}
}
return ret;
}
};
复杂度分析
| 项目 | 复杂度 |
|---|---|
| 时间复杂度 | O(N^2+M) |
| 空间复杂度 | O(M) |
总结
二维差分核心:
- 区间加操作 → O(1) 修改差分数组的 4 个角点
- 最后求二维前缀和还原 → O(n²)
关键细节:
- 差分数组直接复用在结果矩阵上,无需额外空间
- 边界判断
x2+1 < n防止越界 - 还原公式:
a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1]
使数组所有元素变成1的最少操作次数
问题描述
给你一个下标从 0 开始的 正 整数数组 nums 。你可以对数组执行以下操作任意 次:
选择一个满足 0 <= i < n - 1 的下标 i ,将 nums[i] 或者 nums[i+1] 两者之一替换成它们的最大公约数。
请你返回使数组 nums 中所有元素都等于 1 的 最少 操作次数。如果无法让数组全部变成 1 ,请你返回 -1 。
两个正整数的最大公约数指的是能整除这两个数的最大正整数。
(真题链接:使数组所有元素变成1的最少操作次数)
解题思路
要解决这个问题我们可以换一种方法:
首先我们需要先判断一下是否可以实现把所有元素都变成 1,即所有数的最大公约数是否为 1,如果不可以,不要犹豫,直接返回 -1。
因为这种情况下不可能将所有元素都变成 1。如果可以,我们先寻找一个最小的len,在这个区间内数组的最大公约数为 1,所需要将这个区间中一个元素变成1这个操作的次数就为len - 1;有一个 1 之后,如果要将整个数组都变成1只需要再进行n-1次扩散即可。所有使整个数组变成1的操作次数就是n+len-2,最小操作次数只需要我们找出这个最小的minlen即可。
代码实现
cpp
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size();
int num1 = 0,g = 0;
for(int x : nums)
{
if(x == 1)num1++;
g = gcd(g, x);
}
if(num1 > 0)return n - num1;
if(g > 1)return -1;
// 找到一个最小的可以区间可以使区间所有数组gcd = 1
int minlen = n;
for(int i = 0; i < n; i++)
{
int g = 0;
for(int j = i; j < n; j++)
{
g = gcd(g, nums[j]);
if(g == 1)
{
minlen = min(minlen, j - i + 1);
break;
}
}
}
return minlen + n - 2;
}
};
复杂度分析
| 维度 | 最优解 |
|---|---|
| 时间复杂度 O(n^2 logN) | 双重循环 + gcd 计算(log M 为数值范围) |
| 空间复杂度 O(1) | 无法再优化,因为不需要额外存储 |
总结
三步走策略:
预判断
统计已有的 1 的个数num1
计算全局 gcd,若 >1 则返回 -1(不可能全变成 1)
特殊情况
如果已有 1,直接返回n - num1(每个 1 可扩散一次)
一般情况
寻找最短的gcd=1的子数组,长度为minlen需要minlen - 1次操作在该区间内造出一个 1,再用n - 1次操作将这个 1 扩散到整个数组
总操作数 = minlen + n - 2