LeetCode2588之统计美丽子数组数目(相关话题:前缀异或和)

题目描述

给你一个下标从 0 开始的整数数组nums 。每次操作中,你可以:

  • 选择两个满足 0 <= i, j < nums.length 的不同下标 i 和 j 。
  • 选择一个非负整数 k ,满足 nums[i] 和 nums[j] 在二进制下的第 k 位(下标编号从 0 开始)是 1 。
  • 将 nums[i] 和 nums[j] 都减去 2k 。

如果一个子数组内执行上述操作若干次后,该子数组可以变成一个全为 0 的数组,那么我们称它是一个 美丽 的子数组。

请你返回数组 nums 中 美丽子数组 的数目。

子数组是一个数组中一段连续 非空 的元素序列。

示例 1:

输入:nums = [4,3,1,2,4]
输出:2
解释:nums 中有 2 个美丽子数组:[4,3,1,2,4] 和 [4,3,1,2,4] 。
- 按照下述步骤,我们可以将子数组 [3,1,2] 中所有元素变成 0 :
  - 选择 [3, 1, 2] 和 k = 1 。将 2 个数字都减去 21 ,子数组变成 [1, 1, 0] 。
  - 选择 [1, 1, 0] 和 k = 0 。将 2 个数字都减去 20 ,子数组变成 [0, 0, 0] 。
- 按照下述步骤,我们可以将子数组 [4,3,1,2,4] 中所有元素变成 0 :
  - 选择 [4, 3, 1, 2, 4] 和 k = 2 。将 2 个数字都减去 22 ,子数组变成 [0, 3, 1, 2, 0] 。
  - 选择 [0, 3, 1, 2, 0] 和 k = 0 。将 2 个数字都减去 20 ,子数组变成 [0, 2, 0, 2, 0] 。
  - 选择 [0, 2, 0, 2, 0] 和 k = 1 。将 2 个数字都减去 21 ,子数组变成 [0, 0, 0, 0, 0] 。

示例 2:

输入:nums = [1,10,4]
输出:0
解释:nums 中没有任何美丽子数组。

提示:

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 106

解题思路

  1. 相同的数相互异或等于0
  2. 0和任何数异或都等于它本身

由于每次可以执行的操作是选择数组中某一位均为1的两个数,将其减去2^{k} ,于是可以想到位运算的异或操作,相同的数异或结果为0


如果子数组异或结果为0,那么该子数组为美丽子数组,因此可以将题意转换为,求出子数组异或结果为0的个数

借鉴前缀和数组的概念,我们可以记录nums[i]对应的异或数组,假设nums[0,i]对应的异或值为a,nums[0,j]对应的异或值也为为a,那么nums[i+1,j]对应的异或值为0【任何一个数与0异或的结果还为0】

因此,我们枚举子数组的每一个右端点iii,将nums[0,i-1]的异或结果存放在哈希表中,哈希表的键值为异或结果,key值为出现的次数,那么以nums[i]为右端点的美丽子数组的数目即为其对应的异或结果在这之前出现的次数

代码实现 

class Solution {
    public long beautifulSubarrays(int[] nums) {
        long res = 0L;
        Map<Integer,Long> map = new HashMap<>();
        map.put(0, 1L);
        int cur = 0;
        for (int i = 0; i < nums.length; i++){
            cur ^= nums[i];
            res += map.getOrDefault(cur, 0L);
            map.put(cur, map.getOrDefault(cur, 0L) + 1);
        }
        return res;

    }
}

题目拓展

给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

  • 子数组大小 至少为 2 ,且
  • 子数组元素总和为 k 的倍数。

如果存在,返回 true ;否则,返回 false 。

如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。

解题思路

同余定理:如果两个整数m、n满足n-m能被k整除,那么n和m对k同余

即 ( pre(j) - pre (i) ) % k == 0 则 pre(j) % k == pre(i) % k

  • 计算前缀和 pre( j ) % k

  • 当pre(j) % k 在哈希表中已存在,则说明此时存在 i 满足 pre(j) % k == pre(i) % k ( i < j )HashMap里,已知Key,可以取到Value 即i的值, 最后 判断 j - i >= 2 是否成立 即可

  • 当 pre(j) % k 不存在于哈希表,则将 (pre(j) % k, j ) 存入哈希表

因在计算 pre(i) = (pre(i-1) + nums[i]) % k 时,pre(i) 只与上一个状态有关

故可以直接用变量pre 替代数组。 那么求前缀和 % k 的公式就简化为题解代码中的 remainder = (remainder + nums[i]) % k;

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        int m = nums.length;
        if (m < 2) {
            return false;
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0, -1);
        int remainder = 0;
        for (int i = 0; i < m; i++) {
            remainder = (remainder + nums[i]) % k;
            if (map.containsKey(remainder)) {
                int prevIndex = map.get(remainder);
                if (i - prevIndex >= 2) {
                    return true;
                }
            } else {
                map.put(remainder, i);
            }
        }
        return false;
    }
}

相关题目

LeetCode560之和为K的子数组(相关话题:前缀和)_击水三千里的博客-CSDN博客
1590. 使数组和能被 P 整除
525. 连续数组
面试题 17.05. 字母与数字
1915. 最美子字符串的数目
1371. 每个元音包含偶数次的最长子字符串
1542. 找出最长的超赞子字符串

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值