文章目录

题目描述
示例 1:
输入: nums = [1,1,1], queries = [[0,2,1,4]]
输出: 4
解释:
唯一的查询 [0, 2, 1, 4] 将下标 0 到下标 2 的每个元素乘以 4。
数组从 [1, 1, 1] 变为 [4, 4, 4]。
所有元素的异或为 4 ^ 4 ^ 4 = 4。
示例 2:输入: nums = [2,3,1,5,4], queries = [[1,4,2,3],[0,2,1,2]]
输出: 31
解释:
第一个查询 [1, 4, 2, 3] 将下标 1 和 3 的元素乘以 3,数组变为 [2, 9, 1, 15, 4]。
第二个查询 [0, 2, 1, 2] 将下标 0、1 和 2 的元素乘以 2,数组变为 [4, 18, 2, 15, 4]。
所有元素的异或为 4 ^ 18 ^ 2 ^ 15 ^ 4 = 31。
提示:1 <= n == nums.length <= 103
1 <= nums[i] <= 109
1 <= q == queries.length <= 103
queries[i] = [li, ri, ki, vi]
0 <= li <= ri < n
1 <= ki <= n
1 <= vi <= 105
思路简述
拿到这道题,我们不妨先从数据范围入手------可以看到数组长度 n 和查询数量 q 都在 10³ 以内,这个规模其实非常"友好",意味着我们不需要绞尽脑汁想复杂的优化算法,直接按照题目描述一步步模拟的暴力解法,是完全可以通过所有测试用例的。
不过这里也可以提前跟大家提一句,明天的每日一题"区间乘法查询后的异或 II"会把数据范围大幅扩大,到时候这种暴力模拟的思路就会因为时间复杂度过高而超时,没法 AC 了。但今天我们先专注于把这道基础题吃透,就用最直观的模拟和暴力解法来搞定它,至于优化的算法嘛,明天的难题明天再想,当然感兴趣的朋友也可以提前琢磨琢磨怎么优化~
具体来说,我们的整体思路就是严格复刻题目给出的操作步骤 。首先遍历每一个查询,对于 queries 中的每一组参数:
也就是 queries[i] 里的 li(起始下标)、ri(结束下标)、ki(步长)和 vi(乘数)
我们先把起始下标 idx 初始化为 li。
接下来进入循环,只要 idx 还小于等于 ri,就依次执行两个操作:
- 一是按照题目给的公式更新
nums[idx],即把nums[idx]乘以vi之后对10⁹ + 7取模 - 二是把
idx加上ki,移动到下一个需要更新的位置,直到idx超出ri的范围,这个查询就算处理完了。
这里有两个特别容易踩坑的细节,一定要注意。
第一个是数据溢出问题 :题目里 nums[i] 最大能到 10⁹,vi 最大能到 10⁵,这两个数相乘的结果能到 10¹⁴,但 C++ 里的 int 类型通常是 32 位的,最大只能存到约 2e9,直接相乘肯定会溢出,导致结果变成负数或者乱码。所以我们需要在计算时加上 1LL,或者用 long long 类型来处理中间结果,把运算"升级"成 64 位,这样就能稳稳装下乘积,避免溢出了。
第二个是最后计算异或结果时,要记得把异或的初始值设为 0 ------因为根据异或的规则,x ^ 0 = x(任何数和 0 异或都等于它本身),从 0 开始依次异或数组里的每个元素,最后得到的就是所有元素的异或总和啦。
代码实现
cpp
class Solution {
public:
const int MOD = 1e9 + 7; // 定义模数,防止数值过大
int xorAfterQueries(vector<int>& nums, vector<vector<int>>& queries)
{
// 遍历每个查询
for(int i = 0; i < queries.size(); i++)
{
int idx = queries[i][0]; // 当前查询的起始下标 li
int ri = queries[i][1]; // 当前查询的结束下标 ri
// 按照步长 ki 遍历区间 [li, ri]
while(idx <= ri)
{
int ki = queries[i][2]; // 当前查询的步长 ki
int vi = queries[i][3]; // 当前查询的乘数 vi
// 核心更新逻辑:nums[idx] = (nums[idx] * vi) % MOD
// 1LL 的作用:将运算提升为 64 位,防止中间结果溢出
nums[idx] = (1LL * nums[idx] % MOD * vi % MOD) % MOD;
idx += ki; // 按照步长移动下标
}
}
// 计算最终数组所有元素的异或结果
int ret = 0; // 异或初始值为 0(因为 x ^ 0 = x)
for(auto e : nums)
ret ^= e;
return ret;
}
};
复杂度分析
- 时间复杂度 :O(q * n),其中
q是查询数量,n是数组长度。最坏情况下,每个查询的步长ki = 1,需要遍历整个区间,因此总时间复杂度与查询数量和数组长度的乘积成正比。 - 空间复杂度:O(1),我们直接在原数组上进行修改,不需要额外的辅助空间(不计算输入和输出的空间)。
踩坑记录
- 整数溢出问题:为什么需要
1LL?
在 C++ 中,int通常是 32 位的,最大值约为2e9。而题目中nums[i]可达1e9,vi可达1e5,两者相乘的结果可达1e14,远超过int的范围,直接计算会导致溢出(结果变成负数或乱码)。
错误写法:
cpp
nums[idx] = nums[idx] * vi % MOD;
// 两个 int 相乘,结果仍是 int,先溢出再取模,结果错误
正确写法 1(推荐):
cpp
nums[idx] = (1LL * nums[idx] % MOD * vi % MOD) % MOD;
// 1LL 是 long long 类型,会将整个运算提升为 64 位,避免溢出
正确写法 2:
cpp
nums[idx] = ((long long)nums[idx] * vi) % MOD;
// 先将 nums[idx] 强转为 long long,再相乘,同样能避免溢出
注意:下面这种写法是无效的!
cpp
nums[idx] = (long long)(nums[idx] * vi) % MOD;
// 等价于:
// 第一步:算 int × int → 溢出
// 第二步:溢出后再强转 long long → 已经晚了!
所以不行了 ,然而我们nums[idx] = (long long)nums[idx] * vi % MOD;也是也可的
- 异或运算有一个重要性质:
x ^ 0 = x(任何数与 0 异或,结果都是它本身)。因此,我们将结果变量ret初始化为 0,再依次与数组中的每个元素异或,最终得到的就是所有元素的异或总和。
如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!

