文章目录
- [力扣 2906. 构造乘积矩阵](#力扣 2906. 构造乘积矩阵)
- [力扣 13. 罗马数字转整数](#力扣 13. 罗马数字转整数)
- 踩坑记录

力扣 2906. 构造乘积矩阵
题目描述
示例 1:
输入:grid = [[1,2],[3,4]]
输出:[[24,12],[8,6]]
解释:p[0][0] = grid[0][1] * grid[1][0] * grid[1][1] = 2 * 3 * 4 = 24
p[0][1] = grid[0][0] * grid[1][0] * grid[1][1] = 1 * 3 * 4 = 12
p[1][0] = grid[0][0] * grid[0][1] * grid[1][1] = 1 * 2 * 4 = 8
p[1][1] = grid[0][0] * grid[0][1] * grid[1][0] = 1 * 2 * 3 = 6
所以答案是 [[24,12],[8,6]] 。
示例 2:输入:grid = [[12345],[2],[1]]
输出:[[2],[0],[0]]
解释:p[0][0] = grid[0][1] * grid[0][2] = 2 * 1 = 2
p[0][1] = grid[0][0] * grid[0][2] = 12345 * 1 = 12345. 12345 % 12345 = 0 ,所以 p[0][1] = 0
p[0][2] = grid[0][0] * grid[0][1] = 12345 * 2 = 24690. 24690 % 12345 = 0 ,所以 p[0][2] = 0
所以答案是 [[2],[0],[0]] 。
提示:1 <= n == grid.length <= 105
1 <= m == grid[i].length <= 105
2 <= n * m <= 105
1 <= grid[i][j] <= 109
思路简述
这道题如果我们要用暴力去解决时间复杂度会达到恐怖的 O((nm)²),结合数据范围一定是会超时的,因此需借助前缀积/后缀积 技巧优化(对该思路陌生的朋友可先完成 力扣 238. 除了自身以外数组的乘积 进行铺垫),那道题的算法与这道题目大差不差。
一维数组的核心思路
对于一维数组,目标值可通过 "当前元素左侧所有元素的乘积 × 右侧所有元素的乘积" 得到。我们可通过构建两个辅助数组(或变量)分别存储 前缀积 与 后缀积,从而快速获取每个元素左右两侧的乘积。
通过对前缀积和后缀积数组进行初始化赋值为1,可使其与原数组索引"错位",最终每个位置上前缀积与后缀积的乘积,就会形成当前已知数组下标相同的两个数组正是这个下标的元素的前缀积(左部分乘积)后缀积(右部分乘积),恰好对应该位置"除自身外的乘积"(如下图所示)。

二维数组的优化
将此思路推广至二维数组:我们可在逻辑上将二维数组"拼接"为一个一维长数组,从而复用上述算法,至于如何拼接二维数组其实我们可以直接进行两层for循环进行遍历就可以了。
进一步优化空间复杂度:无需维护完整的前缀积与后缀积数组,只需用 两个变量动态记录当前的前缀积与后缀积,边计算边更新结果,即可将额外空间复杂度降至最低。
代码实现
cpp
class Solution {
public:
vector<vector<int>> constructProductMatrix(vector<vector<int>>& grid)
{
int n = grid.size(), m = grid[0].size();
long long prefix = 1, suffix = 1; // 前缀积、后缀积(用 long long 防止溢出)
vector<vector<int>> ret(n, vector<int>(m, 0));
// 第一次遍历:从右下到左上,计算后缀积
for(int i = n - 1; i >= 0; i--)
{
for(int j = m - 1; j >= 0; j--)
{
ret[i][j] = suffix;
suffix = suffix * grid[i][j] % 12345;
}
}
// 第二次遍历:从左上到右下,计算前缀积并合并结果
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
// 修正:先相乘再取模,避免运算顺序错误
ret[i][j] = (ret[i][j] * prefix) % 12345;
prefix = prefix * grid[i][j] % 12345;
}
}
return ret;
}
};
复杂度分析
- 时间复杂度:O(mn),仅需两次遍历矩阵。
- 空间复杂度:O(mn),主要为结果矩阵的开销(题目要求返回结果,因此不计入额外空间复杂度的话,可认为额外空间为 O(1))。
力扣 13. 罗马数字转整数
题目描述
示例 1:
输入: s = "III"
输出: 3
示例 2:输入: s = "IV"
输出: 4
示例 3:输入: s = "IX"
输出: 9
示例 4:输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:1 <= s.length <= 15
s 仅含字符 ('I', 'V', 'X', 'L', 'C', 'D', 'M')
题目数据保证 s 是一个有效的罗马数字,且表示整数在范围 [1, 3999] 内
题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。
思路简述
结合哈希表,这道题实现方式有很多种,题目中的唯一难点就是罗马数字中存在特殊组合规则,所以要进行特判一下,最原始的特判方式就是用if来进行分类计算,但是这样代码量相较优雅实现方式大,还太麻烦。
对于所有有效的罗马数字,都遵循核心规律:若当前字符对应的值小于下一个字符的值,则当前值需从结果中减去,否则直接加上。利用哈希表预存字符与数值的映射,即可在一次遍历中完成计算。
代码实现
cpp
class Solution {
public:
int romanToInt(string s) {
unordered_map<char,int> hash = {
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000}
};
int ret = 0;
for(int i = 0; i < s.size(); i++)
{
int tmp = hash[s[i]];
// 注意边界判断:i 未到最后一位,且当前值小于下一位值
if(i < s.size() - 1 && tmp < hash[s[i+1]])
ret -= tmp;
else
ret += tmp;
}
return ret;
}
};
复杂度分析
- 时间复杂度:O(n),仅需一次遍历字符串。
- 空间复杂度:O(1),哈希表大小固定(仅7个键值对),与输入长度无关。
踩坑记录
- 构造乘积矩阵的取模顺序:
计算前缀积与后缀积的合并结果时我们是要先求出结果在进行取模简化,所以要我们严格遵循 (前缀积 * 后缀积) % MOD 的顺序,避免因 *= 与 % 的计算顺序错误导致逻辑错误。 - 取模运算的性质:
在无特殊顺序要求时,乘法与取模遵循乘法分配律 / 同余性质((a * b) % MOD = ((a % MOD) * (b % MOD)) % MOD),多次取模不影响最终结果,且能有效防止溢出。 - 罗马数字转换的边界保护:
比较当前字符与下一个字符时,需先判断 i < s.size() - 1,避免因 i+1 导致数组越界访问。
如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!感谢大家的陪伴与支持


