这道题有两个知识点个人认为比较重要。
第一个:
要使任意两元素最终相等,这两元素的差必须是 x 的倍数,否则无法通过加减 x 来相等
亦即:要使两个元素通过加减x来变成相等,它们之间的差一定是x的倍数吗???也就是说上面这个知识点是充要条件吗?
证明:
这个证明比较简单。
结合一下同余定理可知要令最后各个数通过加减x来最终相等,那么可以:
我们可以以数组中的某一元素为基准,若所有元素与它的差均为 x 的倍数,则任意两元素之差为 x 的倍数
第二个:
参考这一个题解:[java]贪心 取中位数(垃圾解释) - 获取单值网格的最小操作数 - 力扣(LeetCode) (leetcode-cn.com)
图来源于[java]贪心 取中位数(垃圾解释) - 获取单值网格的最小操作数 - 力扣(LeetCode) (leetcode-cn.com)
这个问题的证明有点像初中数学:求一个点,到直线上的各个点之间的距离之和最小。
具体证明省略。。。
可参考大神的证明:贪心取中位数的理由 - 获取单值网格的最小操作数 - 力扣(LeetCode) (leetcode-cn.com)
还有这个最小化曼哈顿距离最小化曼哈顿距离姚光超的专栏-CSDN博客曼哈顿距离最小值
结合上面的两个知识点可知:中位数和中位数之间的所有整数到各个点之间的距离之和最小。但是,数组中的数不一定能通过加减x最终变成,比如输入数组[2,4,6,8],x=2。虽然5也是到各个点之和最小,但是数组中的数并不能通过加减x变成5。因此我们直接取中位数就好了。
只要数组中的每个数与数组中的某个数的差是x的倍数, 那么每一个数都能通过加减x变成中位数
中位数解法:
public int minOperations(int[][] grid, int x) {
int[] nums = new int[grid.length * grid[0].length];
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int idx = 0;
// 校验
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
nums[idx++] = grid[i][j];
max = Math.max(grid[i][j], max);
min = Math.min(grid[i][j], min);
if ((grid[i][j] - grid[0][0]) % x != 0) {
return -1;
}
}
}
// 先排序
Arrays.sort(nums);
int mid = nums[nums.length / 2];
int result = 0;
for (int i = 0; i < nums.length; i++) {
result += Math.abs((mid - nums[i]) / x);
}
return result;
}
前缀和解法:
public int minOperations(int[][] grid, int x) {
int[] nums = new int[grid.length * grid[0].length];
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int idx = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
nums[idx++] = grid[i][j];
max = Math.max(grid[i][j], max);
min = Math.min(grid[i][j], min);
if ((grid[i][j] - grid[0][0]) % x != 0) {
return -1;
}
}
}
// 先排序,在求前缀和
Arrays.sort(nums);
int[] prefixSum = new int[grid.length * grid[0].length + 1];
for (int i = 1; i < prefixSum.length; i++) {
prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
}
int result = Integer.MAX_VALUE;
for (int i = 0; i < nums.length; i++) {
// 左边
int deltaLeftSum = (i + 1) * nums[i] - (prefixSum[i + 1] - prefixSum[0]);
// 右边
int deltaRightSum = (prefixSum[nums.length] - prefixSum[i + 1]) - (nums.length - i - 1) * nums[i];
result = Math.min(result, (deltaRightSum + deltaLeftSum) / x);
}
return result;
}