每日一题 力扣 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 <= numsi <= 109

1 <= q == queries.length <= 103

queriesi = 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 → 已经晚了!

所以不行了 ,然而我们numsidx = (long long)numsidx * vi % MOD;也是也可的

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

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

相关推荐
凡人叶枫17 小时前
Effective C++ 条款37:绝不重新定义继承而来的缺省参数值
linux·c++·windows
王老师青少年编程17 小时前
2022年CSP-X复赛真题及题解(T4:摧毁)
c++·真题·csp·信奥赛·复赛·csp-x·摧毁
Yvonne爱编码17 小时前
JAVA EE初阶---DAY 2 计算机网络
java·开发语言·计算机网络·算法·java-ee·php
梓䈑17 小时前
C++大模型统一接入引擎(第三篇):模型管理、会话持久化与SDK门面封装的完整实现
数据库·c++
王燕龙(大卫)17 小时前
使用实时调度策略和无锁队列踩坑记录
c++
赴生-17 小时前
C++进阶 智能指针
开发语言·c++
AI thought17 小时前
C语言、C++与C#深度研究报告:从底层控制到现代企业级开发的演进
c语言·c++·c·内存管理·编译模型
我命由我1234517 小时前
RFID 技术极简理解
java·c语言·c++·嵌入式硬件·物联网·visualstudio·java-ee
workflower17 小时前
基于机器学习的设备故障预测分析方法
人工智能·算法·机器学习·设计模式·语言模型·自然语言处理·重构
格发许可优化管理系统17 小时前
Mentor许可证与其他软件许可证的深度比较
java·大数据·运维·c语言·c++·算法