每日一道leetcode(2026.04.09):区间乘法查询后的异或 II

0 阅读4分钟

1. 题目

给你一个长度为 n 的整数数组 nums 和一个大小为 q 的二维整数数组 queries,其中 queries[i] = [li, ri, ki, vi]。对于每个查询,需要按以下步骤依次执行操作:

设定 idx = li。 当 idx <= ri 时: 更新:nums[idx] = (nums[idx] * vi) % (10e9 + 7)。 将 idx += ki。 在处理完所有查询后,返回数组 nums 中所有元素的 按位异或 结果。

示例 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 <= 10e5 1 <= nums[i] <= 10e9 1 <= q == queries.length <= 10e5 queries[i] = [li, ri, ki, vi] 0 <= li <= ri < n 1 <= ki <= n 1 <= vi <= 10e5

2. 分析

今天这道题是昨天的升级版,题目描述一模一样,只是提示里面的数据量级变了,支持的范围更广了,将昨天的答案拉出来一执行,毫无意外的超时。

之前的版本是纯暴力执行,简单的遍历,我的思路是将相同步长,相同起始点,相同终点的数据,做合并处理,这样能一定程度上减少循环次数,答案是有效的,能解决一定量级和指定用例的问题,但是满足不了定制的测试用例。

class Solution {
    private static final int MOD = 1000000007;

public int xorAfterQueries(int[] nums, int[][] queries) {
        int len = nums.length;
        int from;
        int to;
        int step;
        int ratio;
        Map<String, Integer> map = new HashMap<>();
        for (int[] query : queries) {
            from = query[0];
            to = query[1];
            step = query[2];
            ratio = query[3];
            int count = (to - from + 1) / step;
            if (count > 100 && count > len / 2) {
                // 超过一半时,分成两段
                int mid = (from + to) / 2;
                // 前半段
                this.cacheHandle(from, mid, step, ratio, map);
                // 后半段
                this.cacheHandle(mid + 1, to, step, ratio, map);
                continue;
            }
            this.cacheHandle(from, to, step, ratio, map);
        }
        for (Map.Entry<String, Integer> next : map.entrySet()) {
            String[] split = next.getKey().split(":");
            from = Integer.parseInt(split[0]);
            to = Integer.parseInt(split[1]);
            step = Integer.parseInt(split[2]);
            for (int m = from; m <= to; m += step) {
                nums[m] = (int) (((long) nums[m] * next.getValue()) % MOD);
            }
        }
        int res = 0;
        for (int num : nums) {
            res ^= num;
        }
        return res;
    }

    private void cacheHandle(int from, int to, int step, int ratio, Map<String, Integer> map) {
        String key = from + ":" + to + ":" + step;
        if (!map.containsKey(key)) {
            map.put(key, ratio);
        } else {
            map.put(key, (int) (((long) map.get(key) * ratio) % MOD));
        }
    }
}

在这里插入图片描述 这个解法通过不了最后几个定制的测试用例,即使一分为二,基数还是太大了,我又想着能不能一分为三,将中间大部分共有的给抽出来做批量处理,感觉理论上是可行的,不过也是面向用例编程了。

看了官方的题解,和我的思路差别很大,根号分治+差分的解法,看到这两个词都想一键关机了。根号分治比较好理解,就是当遍历的数据范围不大时,采用暴力解法,大了就采用差分的方式,此题大小的判断使用的是步长与数组长度的平方根就行比较。差分大概的思路是将相同步长的数据合并在一起处理,有正向的乘,就有逆向的除,不过,这里的除不是真的用除法来实现的,而是逆元。具体关于差分的描述这里不多赘述,我不是专业的,也阐述不清楚,哈哈哈。

下面代码实现我贴了官方的解答,其中pow(v, MOD - 2) % MOD就是乘积后求余的反向逆元运算。

3. 代码实现

class Solution {
    private static final int MOD = 1_000_000_007;

    private int pow(long x, long y) {
        long res = 1;
        while (y > 0) {
            if ((y & 1) == 1) {
                res = res * x % MOD;
            }
            x = x * x % MOD;
            y >>= 1;
        }
        return (int) res;
    }

    public int xorAfterQueries(int[] nums, int[][] queries) {
        int n = nums.length;
        int T = (int) Math.sqrt(n);
        List<List<int[]>> groups = new ArrayList<>(T);
        for (int i = 0; i < T; i++) {
            groups.add(new ArrayList<>());
        }

        for (int[] q : queries) {
            int l = q[0], r = q[1], k = q[2], v = q[3];
            if (k < T) {
                groups.get(k).add(new int[]{l, r, v});
            } else {
                for (int i = l; i <= r; i += k) {
                    nums[i] = (int) ((long) nums[i] * v % MOD);
                }
            }
        }

        long[] dif = new long[n + T];
        for (int k = 1; k < T; k++) {
            if (groups.get(k).isEmpty()) {
                continue;
            }
            Arrays.fill(dif, 1);
            for (int[] q : groups.get(k)) {
                int l = q[0], r = q[1], v = q[2];
                dif[l] = dif[l] * v % MOD;
                int R = ((r - l) / k + 1) * k + l;
                dif[R] = dif[R] * pow(v, MOD - 2) % MOD;
            }

            for (int i = k; i < n; i++) {
                dif[i] = dif[i] * dif[i - k] % MOD;
            }
            for (int i = 0; i < n; i++) {
                nums[i] = (int) ((long) nums[i] * dif[i] % MOD);
            }
        }

        int res = 0;
        for (int x : nums) {
            res ^= x;
        }
        return res;
    }
}

在这里插入图片描述

4. 总结

这道题个人觉得浅尝辄止吧,其中涉及的数学运算不懂确实不懂。

不过有几个涉及求余的公式是有必要知道的,在其他算法题中也遇到过几次,不然多次相乘后可能超出有效数值范围,导致最后的结果错误。

乘积求余

(a * b) % c = ((a % c) * (b % c)) % c

商求余

(a / b) % MOD = (a % MOD) * inv(b) % MOD

其中inv(b)为逆元,inv(b) = pow (b, MOD-2, MOD)