给你一个整数数组 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
思路:仔细想想还是有切入点可循的(比赛时完全没想到,真滴菜),在经过一系列的替换后,可以发现最后a[i]=a[i+k]=a[i+2*k]...的,所以我们应该讲数组分成k组并进行动态规划:
我们令dp[i][j]表示遍历到第i组状态为j时的最小替换次数,那么直到当前分组所能构成的异或值之和上一个分组的状态有关,我们分两种情况讨论:
首先假设到第i个分组所能组成的异或值为x,并且到上一个分组的组成的异或值为y,那么这个分组需要组成x^y的异或值。
1⃣️假设x^y这个值并没有在第i个分组里出现过,呢最有解一定是上一个分组所有状态中的最小值与当前分组元素个数的和。
2⃣️若x^y这个值在当前分组出现过,那么我们需要枚举上一个状态以及这个分组出现的所有状态,从而寻求最优解!【注:这里我采用set进行优化,因为直接暴力当前状态的话会超时】
class Solution {
public int minChanges(int[] nums, int k) {
int m = 1 << 10;
int n = nums.length;
int[] dp = new int[m];
Set[] st = new Set[k];
int[] size = new int[k];
int[][] groups = new int[k][m];
for (int i = 0; i < k; i++)
st[i] = new HashSet<Integer>();
for (int i = 0; i < k; i++) {
for (int j = i; j < n; j += k) {
groups[i][nums[j]]++;
st[i].add(nums[j]);
size[i]++;
}
}
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 0; i < k; i++) {
int mn = dp[0];
for (int j = 0; j < m; j++)
mn = Math.min(mn, dp[j]);
int[] dp1 = new int[m];
Arrays.fill(dp1, mn + size[i]);
for (int j = 0; j < m; j++) {
if (dp[j] == Integer.MAX_VALUE)
continue;
Iterator<Integer> it = st[i].iterator();
while (it.hasNext()) {
int val = it.next();
dp1[val ^ j] = Math.min(dp1[val ^ j], dp[j] + size[i] - groups[i][val]);
}
}
for (int j = 0; j < m; j++)
dp[j] = dp1[j];
}
return dp[0];
}
}