1787. 使所有区间的异或结果为零

2023-11-14

1787. 使所有区间的异或结果为零

难度困难72

给你一个整数数组 nums​​​ 和一个整数 k​​​​​ 。区间 [left, right]left <= right)的 异或结果 是对下标位于 left 和 right(包括 left 和 right )之间所有元素进行 XOR 运算的结果:nums[left] XOR nums[left+1] XOR ... XOR nums[right] 。

返回数组中 要更改的最小元素数 ,以使所有长度为 k 的区间异或结果等于零。

 

示例 1:

输入:nums = [1,2,0,3,0], k = 1
输出:3
解释:将数组 [1,2,0,3,0] 修改为 [0,0,0,0,0]

示例 2:

输入:nums = [3,4,5,2,1,7,3,4,7], k = 3
输出:3
解释:将数组 [3,4,5,2,1,7,3,4,7] 修改为 [3,4,7,3,4,7,3,4,7]

示例 3:

输入:nums = [1,2,4,1,2,5,1,2,6], k = 3
输出:3
解释:将数组[1,2,4,1,2,5,1,2,6] 修改为 [1,2,3,1,2,3,1,2,3]

 

提示:

  • 1 <= k <= nums.length <= 2000
  • ​​​​​​0 <= nums[i] < 210

1 / 30下一题解

使所有区间的异或结果为零

力扣官方题解L6

发布于 15 小时前11.2k官方位运算数组哈希表动态规划CC++C#GoJavaJavaScriptPython

方法一:动态规划

思路与算法

设数组 \textit{nums}nums 的长度为 nn。首先我们需要分析出数组 \textit{nums}nums 在修改之后应当具有哪些性质。

由于任意长度为 kk 的区间异或结果等于 00,那么对于任意的 ii,有:

\textit{nums}[i] \oplus \textit{nums}[i+1] \oplus \cdots \oplus \textit{nums}[i+k-1] = 0 \tag{1}nums[i]⊕nums[i+1]⊕⋯⊕nums[i+k−1]=0(1)

以及:

\textit{nums}[i+1] \oplus \textit{nums}[i+2] \oplus \cdots \oplus \textit{nums}[i+k] = 0 \tag{2}nums[i+1]⊕nums[i+2]⊕⋯⊕nums[i+k]=0(2)

其中 \oplus⊕ 表示异或运算。根据异或运算的性质 a \oplus b \oplus b = aa⊕b⊕b=a,将 (1)(2)(1)(2) 两式联立,即可得到:

\textit{nums}[i] \oplus \textit{nums}[i+k] = 0nums[i]⊕nums[i+k]=0

其等价于 \textit{nums}[i] = \textit{nums}[i+k]nums[i]=nums[i+k]。因此我们可以得出一个重要的结论:

数组 \textit{nums}nums 在修改之后是一个以 kk 为周期的周期性数组,即 \forall i \in [0, n-k), \textit{nums}[i] = \textit{nums}[i+k]∀i∈[0,n−k),nums[i]=nums[i+k]。

因此,我们可以将数组 \textit{nums}nums 按照下标对 kk 取模的结果 0, 1, \cdots, k-10,1,⋯,k−1 分成 kk 个组,每个组内的元素必须要相等,并且这 kk 个组对应的元素的异或和为 00,即:

\textit{nums}[0] \oplus \textit{nums}[1] \oplus \cdots \oplus \textit{nums}[k-1] = 0nums[0]⊕nums[1]⊕⋯⊕nums[k−1]=0

只要满足上述的要求,修改后的数组的所有长度为 kk 的区间异或结果一定等于 00。

对于第 ii 个组,我们可以使用哈希映射来存储该组内的元素以及每个元素出现的次数,这样一来,我们就可以尝试使用动态规划来解决本题了。

设 f(i, \textit{mask})f(i,mask) 表示我们已经处理了第 0, 1, \cdots, i0,1,⋯,i 个组,并且这些组对应元素的异或和:

\textit{nums}[0] \oplus \textit{nums}[1] \oplus \cdots \oplus \textit{nums}[i]nums[0]⊕nums[1]⊕⋯⊕nums[i]

为 \textit{mask}mask 的前提下,这些组总计最少需要修改的元素个数。在进行状态转移时,我们可以枚举第 ii 组被修改成了哪个数。假设其被修改成了 xx,那么第 0, 1, \cdots, i-10,1,⋯,i−1 个组对应的元素的异或和即为 \textit{mask} \oplus xmask⊕x,因此我们可以得到状态转移方程:

f(i, \textit{mask}) = \min_x \big\{ f(i-1, \textit{mask} \oplus x) + \text{size}(i) - \text{count}(i, x) \big\}f(i,mask)=xmin​{f(i−1,mask⊕x)+size(i)−count(i,x)}

其中 \text{size}(i)size(i) 表示第 ii 个组里的元素个数,\text{count}(i, x)count(i,x) 表示第 ii 个组里元素 xx 的次数,它们的值都可以通过哈希映射得到。上述状态转移方程的意义即为:如果我们选择将第 ii 组全部修改为 xx,那么有 \text{count}(i, x)count(i,x) 个数是无需修改的,这样就需要修改 \text{size}(i) - \text{count}(i, x)size(i)−count(i,x) 次。

动态规划的时间复杂度是多少呢?我们发现这并不好估计,这是因为 xx 可以很小,也可以很大,它并没有一个固定的范围。然而我们可以发现,题目中规定了 \textit{nums}[i] < 2^{10}nums[i]<210,也就是 \textit{nums}[i]nums[i] 的二进制表示的位数不超过 1010。因此我们可以断定,xx 的上限就是 2^{10}210。

如果 x \geq 2^{10}x≥210,那么 xx 的二进制表示的最高位 11 是在数组 \textit{nums}nums 的其它元素中没有出现过的,因此根据异或的性质,要想将最终的异或结果变为 00,我们需要将另一个组的所有元素改为 yy,且 yy 有与 xx 相同的高位 11。这样一来,我们不如将 xx 和 yy 的高位 11 移除,让它们都在 [0, 2^{10})[0,210) 的范围内,这样更容易增大 \text{count}(i, x)count(i,x),减少最终的修改次数。

当我们确定了 xx 的上限,就可以计算出动态规划的时间复杂度了。状态的数量有 k \times 2^{10}k×210 个,对于每一个状态,我们需要枚举 xx 来进行状态转移,而 xx 的数量同样有 2^{10}210 个,因此时间复杂度为 O(2^{20} k) = O(2^{20} n)O(220k)=O(220n),数量级约为 2 \times 10^92×109,超出了时间限制,因此我们必须要进行优化。

优化

对于未优化的状态转移方程:

f(i, \textit{mask}) = \min_x \big\{ f(i-1, \textit{mask} \oplus x) + \text{size}(i) - \text{count}(i, x) \big\}f(i,mask)=xmin​{f(i−1,mask⊕x)+size(i)−count(i,x)}

首先 \text{size}(i)size(i) 是与 xx 无关的常量,我们可以将其移出最小值的限制,即:

f(i, \textit{mask}) = \text{size}(i) + \min_x \big\{ f(i-1, \textit{mask} \oplus x) - \text{count}(i, x) \big\}f(i,mask)=size(i)+xmin​{f(i−1,mask⊕x)−count(i,x)}

由于我们需要求的是「最小值」,因此在状态转移方程中添加若干大于等于「最小值」的项,对最终的答案不会产生影响。

考虑 \text{count}(i, x)count(i,x) 这一项,如果 xx 没有在哈希表中出现,那么这一项的值为 00,否则这一项的值大于 00。即:

  • 如果 xx 没有在哈希表中出现,那么转移的状态为:

    f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x)

  • 如果 xx 在哈希表中出现,那么转移的状态为:

    f(i-1, \textit{mask} \oplus x) - \text{count}(i, x)f(i−1,mask⊕x)−count(i,x)

    它严格小于 f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x)。如果我们在状态转移方程中添加 f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x),最终的答案不会变化。

因此我们可以将状态转移方程变化为:

f(i, \textit{mask}) = \text{size}(i) + \min\{ t_1, t_2 \}f(i,mask)=size(i)+min{t1​,t2​}

其中 t_1t1​ 对应 xx 在哈希表中出现的状态,即:

t_1 = \min_{x \in \text{HashTable}(i)} \big\{ f(i-1, \textit{mask} \oplus x) - \text{count}(i, x) \big\}t1​=x∈HashTable(i)min​{f(i−1,mask⊕x)−count(i,x)}

t_2t2​ 对应 xx 不在哈希表中出现的状态,以及 xx 在哈希表中出现并且我们额外添加的状态,即:

t_2 = \min_x f(i - 1, \textit{mask} \oplus x)t2​=xmin​f(i−1,mask⊕x)

由于 \textit{mask}mask 的取值范围是 [0, 2^{10})[0,210),xx 的取值范围也是 [0, 2^{10})[0,210),因此 \textit{mask} \oplus xmask⊕x 的取值范围就是 [0, 2^{10})[0,210),与 xx 无关。也就是说:

t_2t2​ 就是所有状态 f(i-1, ..)f(i−1,..) 中的最小值。

那么优化后的动态规划的时间复杂度是多少呢?对于第 ii 组,我们需要计算出第 i-1i−1 组对应的状态 f(i-1, ..)f(i−1,..) 中的最小值,时间复杂度为 O(2^{10})O(210),即为 t_2t2​ 部分的状态转移。而对于 t_1t1​ 部分,状态转移的次数等于第 ii 组哈希映射的大小,小于等于 \text{size}(i)size(i)。因此总的时间复杂度为:

O\left( \sum_{i=0}^{k-1} \big( 2^{10} + \text{size}(i) \big) \right) = O(2^{10}k + n)O(i=0∑k−1​(210+size(i)))=O(210k+n)

最终的答案即为 f(k-1, 0)f(k−1,0)。

细节

由于 f(i, ..)f(i,..) 只会从 f(i-1, ..)f(i−1,..) 转移而来,因此我们可以使用两个长度为 2^{10}210 的一维数组代替二维数组,交替地进行状态转移,减少空间复杂度。

此外,当 i=0i=0 时,f(-1, ..)f(−1,..) 都是需要特殊考虑的边界条件。由于 f(-1, ..)f(−1,..) 表示没有考虑任何组时的异或和,因此该异或和一定为 00,即 f(-1, 0) = 0f(−1,0)=0。其它的状态都是不合法的状态,我们可以将它们赋予一个极大值 \infty∞。

c++

class Solution {
private:
    // x 的范围为 [0, 2^10)
    static constexpr int MAXX = 1 << 10;
    // 极大值,为了防止整数溢出选择 INT_MAX / 2
    static constexpr int INFTY = INT_MAX / 2;
    
public:
    int minChanges(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> f(MAXX, INFTY);
        // 边界条件 f(-1,0)=0
        f[0] = 0;
        
        for (int i = 0; i < k; ++i) {
            // 第 i 个组的哈希映射
            unordered_map<int, int> cnt;
            int size = 0;
            for (int j = i; j < n; j += k) {
                ++cnt[nums[j]];
                ++size;
            }

            // 求出 t2
            int t2min = *min_element(f.begin(), f.end());

            vector<int> g(MAXX, t2min);
            for (int mask = 0; mask < MAXX; ++mask) {
                // t1 则需要枚举 x 才能求出
                for (auto [x, countx]: cnt) {
                    g[mask] = min(g[mask], f[mask ^ x] - countx);
                }
            }
            
            // 别忘了加上 size
            for_each(g.begin(), g.end(), [&](int& val) {val += size;});
            f = move(g);
        }

        return f[0];
    }
};

python

class Solution:
    def minChanges(self, nums: List[int], k: int) -> int:
        # x 的范围为 [0, 2^10)
        MAXX = 2**10
        
        n = len(nums)
        f = [float("inf")] * MAXX
        # 边界条件 f(-1,0)=0
        f[0] = 0
        
        for i in range(k):
            # 第 i 个组的哈希映射
            count = Counter()
            size = 0
            for j in range(i, n, k):
                count[nums[j]] += 1
                size += 1

            # 求出 t2
            t2min = min(f)

            g = [t2min] * MAXX
            for mask in range(MAXX):
                # t1 则需要枚举 x 才能求出
                for x, countx in count.items():
                    g[mask] = min(g[mask], f[mask ^ x] - countx)
            
            # 别忘了加上 size
            f = [val + size for val in g]

        return f[0]

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

1787. 使所有区间的异或结果为零 的相关文章

  • 用Ai描摹图片

    用Ai描摹图片 陈子龙 2019 2 4 用ai来描摹这张图片 先用钢笔工具把哆啦A梦的外面黑的地方钩画出来 并上色 然后在把哆啦A梦的身体蓝色的地方用钢笔描出来 在把它白色的部位用钢笔描出
  • C语言中堆内存的申请和使用

    在编程过程中 有时需要使用大量数据 此时可以使用堆内存来方便存储和管理这些数据 堆内存是由程序员手动进行申请 释放的内存 它的空间非常大 但如果在申请后没有释放 会导致内存泄露 关于堆内存的常用函数 1 void malloc size t
  • 一文读懂微服务架构设计

    一 前言 微服务 MicroServices 是一种架构风格 一个大型复杂软件应用由多个微服务和前端展示层组成 系统中的各个微服务可被独立部署 各个微服务之间是松耦合的 每个微服务仅关注于完成一件任务并很好地完成该任务 在所有情况下 每个任

随机推荐

  • 组合预测模型

    组合预测模型 ARIMA CNN LSTM时间序列预测 Python 目录 组合预测模型 ARIMA CNN LSTM时间序列预测 Python 预测结果 基本介绍 程序设计 参考资料 预测结果 基本介绍 ARIMA CNN LSTM是一种
  • Django运行服务报NameError: name ‘os‘ is not defined-已解决

    这里调用了os模块 但是文件头并没引用os模块 解决办法 在settings py文件头加上 import os
  • 【MySQL】解决JDBC无法成功连接MySQL5.7的问题

    写在前面 笔者的个人主页近期升级了一下服务器 以前的VPS确实不行了 然后也就顺便用了最新版本也就是MySQL5 7 但是这个版本呢升级了很多安全策略 网上的资料 中文 也相对较少 之前因为安装这个MySQL5 7已经折腾了我大半天 这里附
  • CSS深入理解之line-height

    以下文字整理自慕课网 张鑫旭的 CSS深入理解之line height 我看到不时有人点赞收藏这篇文章 我想应该也有很多人是对line height 和vertical align 困惑吧 你们可以去看下这篇文章 上面有我学习vertica
  • texstudio更新记录

    Ubuntu20 04 更新TexStudio 本着不折腾不舒服的原则 准备更新texstudio 原版本2 12 22 texstudio网站上是没找到Ubuntu的 只有xubuntu版本的安装包 既然推荐用ppa方式 那就试试 点开紫
  • Windows Server间文件实时备份(syncthing) ---带历史版本“后悔药”

    一 概念简介 syncthing 一款开源免费的数据同步工具 基于P2P的跨平台文件同步工具 通过tcp建立设备连接 再通过TLS进行数据安全传输 支持公网与局域网搭建 支持单双向同步与历史版本控制 后悔药 支持Android Linux
  • go 进阶 三方库之 gorm

    目录 一 初始化 二 增删改查示例 Save与Update区别 GORM中的钩子 GORM Context支持 GORM 与锁 GORM的预加载Preload与Joins 查询时优雅的处理动态条件 分页 gorm io plugin扩展包
  • 【DFS】1905. 统计子岛屿

    1905 统计子岛屿 解题思路 如果两个岛屿的点不一样 说明grid2这个岛屿一定不是子岛屿 然后淹没i j 以及相邻的土地 现在grid2 剩下的岛屿 全部都是子岛屿 计算岛屿的数量 dfs计算陆地数量 class Solution pu
  • java类本身自己,如何在数据库中使用自己的 Java 类?

    如何在数据库中使用自己的 Java 类 Java 语言比 SQL 更强大 Java 是一种面向对象的语言 因此它的指令 源代码 采用类的形式 要在数据库中执行 Java 应在数据库外编写 Java 指令并在数据库外将它们编译为已编译的类 字
  • 2022秋招笔试加面经合集,不区分公司,不定期更新

    9 9日mark 秋招陆陆续续开始 我自己的定位首先是国企然后是互联网企业 这里把面试和笔试整理下 攒人品 废话不多说开始 首先说一下简历吧 很多同学可能投后台 测试 算法都是一个简历 这样对自己来说是很方便 但是用通用的简历就会导致面试官
  • keil 4单片机程序的debug调试

    1 单击keil4窗口的调试按钮快捷图标 进入到软件模拟调试模式 如图所示 在软件调试模式下 可以设置断点 单步 全速 进入某个函数内部运行 还可以查看变量的变化过程 模拟硬件IO口电平变化 查看代码执行时间等 先了解一下调试按钮的功能 其
  • KMP算法详解(参考代码随想录)

    KMP算法详解 参考代码随想录 KMP的经典思想 当出现字符串不匹配时 可以记录一部分之前已经匹配的文本内容 利用这些信息避免从头再去做匹配 前缀表 前缀表是用来回退的 它记录了模式串与主串 文本串 不匹配的时候 模式串应该从哪里开始重新匹
  • 【每日一学】浮动IP

    在集群或者主备双机场景 对服务使用者而言期望的只有一个IP或域名 这个时候需要的就是浮动IP 一 主备实现 利用单个网卡绑定多个ip地址的技术和crontab自动执行技术 为主机的网卡多绑定一个静态ip 如124 158 26 32 这个地
  • MySQL锁定状态查看相关命令

    1 SHOW PROCESSLIST 显示哪些线程正在运行 只列出前100条 SHOW FULL PROCESSLIST 列出所有线程信息 如果您有SUPER权限 您可以看到所有线程 否则 您只能看到您自己的线程 也就是 与您正在使用的My
  • SpringBoot项目在logback.xml中读取配置中的日志路径问题

    一 问题 在SpringBoot项目 使用logback xml中配置日志的存储位置时 读取application properties或application yml中配置的路径 在logback xml中引用如下
  • PCB 经验

    1 CPU或是关键的IC放在PCB的board中间 目的是有足够空间布线 2 CPU和内寸之间走线一般要做等长走线 长度也要考虑是否够绕线 3 时钟芯片尽量靠近CPU 并远离其它敏感信号 4 CPU的复位电路尽量远离时钟以及其它高速信号 5
  • JS 闭包问题

    var result function foo1 var i 0 for i lt 3 i i 1 result i function j return function console log log 111 gt j i foo1 re
  • gtest里面的断言EXPECT_EQ和ASSERT_EQ的区别

    tips 主要用于记录工作中遇到的问题及解决方案 最近刚开始使用gtest 对里面的断言EXPECT EQ和ASSERT EQ的区别有疑惑 故记录下来 以备后续查看 TEST Binary test std string strPath O
  • Proxy error Could not proxy request错误解决

    原因 跨域 解决 package json文件中的scripts调试添加 start node index js server nodemon index js ignore client
  • 1787. 使所有区间的异或结果为零

    1787 使所有区间的异或结果为零 难度困难72 给你一个整数数组 nums 和一个整数 k 区间 left right left lt right 的 异或结果 是对下标位于 left 和 right 包括 left 和 right 之间