leetcode 2033. 获取单值网格的最小操作数 题解(两个重要知识点,取中位数好理解一点)

398 阅读3分钟

这道题有两个知识点个人认为比较重要。

第一个:

要使任意两元素最终相等,这两元素的差必须是 x 的倍数,否则无法通过加减 x 来相等

亦即:要使两个元素通过加减x来变成相等,它们之间的差一定是x的倍数吗???也就是说上面这个知识点是充要条件吗?

证明:

这个证明比较简单。

假设有两个整数abx要使得a+ax=b+bxba=axbxba=(ab)x。易知a,bx都是整数,所以是成立的假设有两个整数a,b和x。\\ 要使得a + a'x = b + b'x \\ b - a = a'x - b'x \\ 有b - a = (a'-b')x。 易知a',b'和x都是整数,所以是成立的

结合一下同余定理可知要令最后各个数通过加减x来最终相等,那么可以:

我们可以以数组中的某一元素为基准,若所有元素与它的差均为 x 的倍数,则任意两元素之差为 x 的倍数

第二个:

参考这一个题解:[java]贪心 取中位数(垃圾解释) - 获取单值网格的最小操作数 - 力扣(LeetCode) (leetcode-cn.com)

1633843495-oEjmUY-bd68d7f4e59501f83d7cb4643d620e7.jpg ​ 图来源于[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;
    }