力扣1787题:使所有区间的异或结果为零-动态规划

一、题目描述

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/make-the-xor-of-all-segments-equal-to-zero

示例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] < 2^10

二、解题思路

由于长度为k的区间异或结果为0,所以我们可以推导出:

n u m s [ i ] ⨁ n u m s [ i + 1 ] ⨁ . . . ⨁ n u m s [ i + k − 1 ] = 0 nums[i] \bigoplus nums[i+1] \bigoplus ...\bigoplus nums[i+k-1] = 0 nums[i]nums[i+1]...nums[i+k1]=0

n u m s [ i + 1 ] ⨁ n u m s [ i + 2 ] ⨁ . . . ⨁ n u m s [ i + k ] = 0 nums[i+1] \bigoplus nums[i+2] \bigoplus ...\bigoplus nums[i+k] = 0 nums[i+1]nums[i+2]...nums[i+k]=0

两个式子异或得: n u m s [ i ] ⨁ n u m s [ i + k ] = 0 nums[i] \bigoplus nums[i+k] = 0 nums[i]nums[i+k]=0,所以 n u m s [ i ] = n u m s [ i + k ] nums[i] = nums[i+k] nums[i]=nums[i+k]

由这个结果我们可以推倒出最终要改成的数组nums是k个数字循环组成的数组。所以数组下标模k相同的分为一组可以先举个例子,比如数组为nums = [3,4,7,3,1,7,3,5], k = 3。则分为 k = 3 k = 3 k=3组,以数组下标模k相同的分为一组,如下图表示更为形象:
在这里插入图片描述

最终我们要变成如下图所示:
在这里插入图片描述

即每列(组)中元素相等,每行(数量为k)的异或结果为0,满足这两个条件即可

1. 定义状态

​ 设dp[i][j]表示前i组的首行的异或结果为j的最少更改元素次数,为什么是首行呢?因为每组(列)的元素是相同,组号i从0开始,即0 <= i <= k-1; 有题目提示可得j的范围是0到210。 那么我们最终要求出的是dp[k-1][0]即为前k-1组的元素异或结果为0的最少更改元素次数。

​ 设每组元素个数为gcount,当然除最后一组可能例外每组元素的个数都输相同,我们需要使用哈希表统计每组元素出现的频率,哈希表用map表示,如获取元素j出现的频率可表示为map.get(j)即可。

  • 情况一:当i = 0时,即第0组,可得如下公式:

    d p [ 0 ] [ j ] = g c o u n t − m a p . g e t ( j ) , 0 ≤ j < 2 10 dp[0][j] = gcount - map.get(j),0 \leq j < 2^{10} dp[0][j]=gcountmap.get(j),0j<210

    解析一下这个很好理解就是每组元素的总数减去元素j出现的频率就是要修改的最少次数。

  • 情况二:当0 < i < k时,一部分是前 i - 1 列的修改次数,一部分是当前列的修改次数。

    这时候需要分情况讨论:

    • 只有当前列部分上元素修改则有如下式子:

      d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ⨁ p ] + g c o u n t − m a p . g e t ( p ) ) dp[i][j] = min(dp[i-1][j \bigoplus p] + gcount - map.get(p)) dp[i][j]=min(dp[i1][jp]+gcountmap.get(p))

    • 当前列中所有都需替换则有如下式子:

      d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ a n y ] ) + g c o u n t dp[i][j] = min(dp[i-1][any]) + gcount dp[i][j]=min(dp[i1][any])+gcount

2. 定义状态转移方程

  • 当i = 0时,有

d p [ 0 ] [ j ] = g c o u n t − m a p . g e t ( j ) , 0 ≤ j < 2 10 dp[0][j] = gcount - map.get(j),0 \leq j < 2^{10} dp[0][j]=gcountmap.get(j),0j<210

  • 当0 < i < k时,有

    • 只有当前列部分上元素修改则有如下式子,即map.get§ > 0时:

      d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ⨁ p ] + g c o u n t − m a p . g e t ( p ) ) dp[i][j] = min(dp[i-1][j \bigoplus p] + gcount - map.get(p)) dp[i][j]=min(dp[i1][jp]+gcountmap.get(p))

    • 当前列中所有都需替换则有如下式子,即map.get§ = 0时:

      d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ⨁ p ] ) + g c o u n t dp[i][j] = min(dp[i-1][j \bigoplus p]) + gcount dp[i][j]=min(dp[i1][jp])+gcount

3. 初始化

​ 当 i = 0时,执行式子进行初始化

4. 计算方式

​ 自上向下,自左向右计算dp数组

三、代码实现

public class Solution {

    public static int MAX_VALUE = (int) Math.pow(2,10);

    public int minChanges(int[] nums, int k) {
        int[][] currentDp = new int[k][MAX_VALUE];
        int[] gDp = new int[k];
        //初始化值为无穷大
        for(int i = 0; i < k; i++){
            Arrays.fill(currentDp[i],(int)Math.pow(2,20));
            gDp[i] = (int)Math.pow(2,20);
        }

        //进行计算dp数组
        for(int i = 0 ; i < k; i++){
            //保存每组元素出现的频率
            Map<Integer,Integer> frequency = new HashMap<>();
            //保存每组元素数量
            int gcount = 0;
            //对每一组元素进行频率统计
            for(int g = i; g < nums.length; g+=k ){
                frequency.put(nums[g],frequency.getOrDefault(nums[g],0) + 1);
                gcount++;
            }
            //初始化第0组数据
            if(i == 0){
                for(int  j= 0; j < MAX_VALUE; j++){
                    currentDp[0][j] = gcount - frequency.getOrDefault(j,0);
                    gDp[0] = Math.min(gDp[0],currentDp[0][j]);
                }
                continue;
            }
            //其它组正常计算
            for(int j = 0; j < MAX_VALUE; j++){
                //默认设置为整组都替换进行初始赋值
                currentDp[i][j] = gDp[i-1] + gcount;
                for(int p : frequency.keySet()){
                    currentDp[i][j] = Math.min(currentDp[i][j],currentDp[i-1][j ^ p] + gcount - frequency.getOrDefault(p,0));
                }
                gDp[i] = Math.min(gDp[i],currentDp[i][j]);
            }
        }
        return currentDp[k-1][0];
    }

    public static void main(String[] args){
        int[] nums = new int[]{26,19,19,28,13,14,6,25,28,19,0,15,25,11};
        int k = 3;
        Solution solution = new Solution();
        System.out.println(solution.minChanges(nums,k));
    }
}

四、优化

由于dp[i][j]只取决于dp[i-1][j]有关,所以我们可以将二维数据压缩到一维数组,代码如下所示:

public class Solution1 {

    public static int MAX_VALUE = (int) Math.pow(2, 10);

    public int minChanges(int[] nums, int k) {
        int[] parentDp = new int[MAX_VALUE];
        int[] currentDp = new int[MAX_VALUE];
        int[] gDp = new int[k];
        //初始化值为无穷大
        Arrays.fill(parentDp, (int) Math.pow(2, 20));
        Arrays.fill(currentDp, (int) Math.pow(2, 20));
        Arrays.fill(gDp, (int) Math.pow(2, 20));

        //进行计算dp数组
        for (int i = 0; i < k; i++) {
            //保存每组元素出现的频率
            Map<Integer, Integer> frequency = new HashMap<>();
            //保存每组元素数量
            int gcount = 0;
            //对每一组元素进行频率统计
            for (int g = i; g < nums.length; g += k) {
                frequency.put(nums[g], frequency.getOrDefault(nums[g], 0) + 1);
                gcount++;
            }
            //初始化第0组数据
            if (i == 0) {
                for (int j = 0; j < MAX_VALUE; j++) {
                    parentDp[j] = gcount - frequency.getOrDefault(j, 0);
                    gDp[0] = Math.min(gDp[0], parentDp[j]);
                }
                continue;
            }
            //其它组正常计算
            for (int j = 0; j < MAX_VALUE; j++) {
                //默认设置为整组都替换进行初始赋值
                currentDp[j] = gDp[i - 1] + gcount;
                for (int p : frequency.keySet()) {
                    currentDp[j] = Math.min(currentDp[j], parentDp[j ^ p] + gcount - frequency.getOrDefault(p, 0));
                }
                gDp[i] = Math.min(gDp[i], currentDp[j]);
            }
            System.arraycopy(currentDp, 0, parentDp, 0, MAX_VALUE);
        }
        return parentDp[0];
    }

    public static void main(String[] args) {
        int[] nums = new int[]{1,2,0,3,0};
        int k = 1;
        Solution1 solution = new Solution1();
        System.out.println(solution.minChanges(nums, k));
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值