每日一题 力扣 3653. 区间乘法查询后的异或 I 模拟 数学 位运算 C++ 题解

文章目录

题目描述

题目链接:力扣 3653. 区间乘法查询后的异或 I

示例 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),我们直接在原数组上进行修改,不需要额外的辅助空间(不计算输入和输出的空间)。

踩坑记录

  1. 整数溢出问题:为什么需要 1LL
    在 C++ 中,int 通常是 32 位的,最大值约为 2e9。而题目中 nums[i] 可达 1e9vi 可达 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;也是也可的

  1. 异或运算有一个重要性质:x ^ 0 = x (任何数与 0 异或,结果都是它本身)。因此,我们将结果变量 ret 初始化为 0,再依次与数组中的每个元素异或,最终得到的就是所有元素的异或总和。

如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!

相关推荐
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-栈》--65.删除字符中的所有相邻重复项,66.比较含退格的字符串,67.基本计算器II,68.字符串解码,69.验证栈序列
c++·算法·
橘子编程2 小时前
编程语言全指南:从C到Rust
java·c语言·开发语言·c++·python·rust·c#
XiYang-DING2 小时前
【LeetCode】102.二叉树的层序遍历
算法·leetcode·职场和发展
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(三):Linux线程 VS 进程,线程控制
java·linux·运维·服务器·c++·学习·ubuntu
计算机安禾2 小时前
【数据结构与算法】第33篇:交换排序(二):快速排序
c语言·开发语言·数据结构·数据库·算法·矩阵·排序算法
洛水水2 小时前
高性能网络编程:io_uring vs epoll、QPS测试工具实现与10道网络面试题解析
c++·udp·tcp·io_uring
沙雕不是雕又菜又爱玩2 小时前
leetcode第12、13、14、15题(C++)
c++·算法·leetcode
睡一觉就好了。2 小时前
C++多态
c++
汀、人工智能2 小时前
[特殊字符] 第50课:最大路径和
数据结构·算法·数据库架构·图论·bfs·最大路径和