周赛340(相同元素分组+前缀和,二分答案)

题解:0x3f https://leetcode.cn/problems/sum-of-distances/solution/zhao-ban-2602-ti-by-endlesscheng-6pbi/

周赛340

6361. 对角线上的质数

难度简单2

给你一个下标从 0 开始的二维整数数组 nums

返回位于 nums 至少一条 对角线 上的最大 质数 。如果任一对角线上均不存在质数,返回 0 。

注意:

  • 如果某个整数大于 1 ,且不存在除 1 和自身之外的正整数因子,则认为该整数是一个质数。
  • 如果存在整数 i ,使得 nums[i][i] = val 或者 nums[i][nums.length - i - 1]= val ,则认为整数 val 位于 nums 的一条对角线上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ml4vtRG6-1681032667115)(null)]

在上图中,一条对角线是 [1,5,9] ,而另一条对角线是 [3,5,7]

示例 1:

输入:nums = [[1,2,3],[5,6,7],[9,10,11]]
输出:11
解释:数字 1、3、6、9 和 11 是所有 "位于至少一条对角线上" 的数字。由于 11 是最大的质数,故返回 11 。

示例 2:

输入:nums = [[1,2,3],[5,17,7],[9,11,10]]
输出:17
解释:数字 1、3、9、10 和 17 是所有满足"位于至少一条对角线上"的数字。由于 17 是最大的质数,故返回 17 。

提示:

  • 1 <= nums.length <= 300
  • nums.length == numsi.length
  • 1 <= nums[i][j] <= 4*106

暴力枚举,然后判断

  • 预处理4e6范围内的质数会超时
class Solution {
    public int diagonalPrime(int[][] nums) {
        int res = 0;
        int n = nums.length;
        for(int i = 0; i < n; i++){
            if(nums[i][i] != 1 && isp(nums[i][i])) 
                res = Math.max(res, nums[i][i]);
            if(nums[i][n - i - 1] != 1 && isp(nums[i][n - i - 1])) 
                res = Math.max(res, nums[i][n - i - 1]);
        }
        return res;
    }
    
    public boolean isp(int n){
        int k = 2;
        while(k * k <= n){
            if(n % k == 0) return false;
            k++;
        }
        return true;
    }
    
}

😭6360. 等值距离和

难度中等5

给你一个下标从 0 开始的整数数组 nums 。现有一个长度等于 nums.length 的数组 arr 。对于满足 nums[j] == nums[i]j != i 的所有 jarr[i] 等于所有 |i - j| 之和。如果不存在这样的 j ,则令 arr[i] 等于 0

返回数组 arr

示例 1:

输入:nums = [1,3,1,1,2]
输出:[5,0,3,4,0]
解释:
i = 0 ,nums[0] == nums[2] 且 nums[0] == nums[3] 。因此,arr[0] = |0 - 2| + |0 - 3| = 5 。 
i = 1 ,arr[1] = 0 因为不存在值等于 3 的其他下标。
i = 2 ,nums[2] == nums[0] 且 nums[2] == nums[3] 。因此,arr[2] = |2 - 0| + |2 - 3| = 3 。
i = 3 ,nums[3] == nums[0] 且 nums[3] == nums[2] 。因此,arr[3] = |3 - 0| + |3 - 2| = 4 。 
i = 4 ,arr[4] = 0 因为不存在值等于 2 的其他下标。

示例 2:

输入:nums = [0,5,3]
输出:[0,0,0]
解释:因为 nums 中的元素互不相同,对于所有 i ,都有 arr[i] = 0 。

提示:

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 109

相同元素分组+前缀和

和 2602. 使数组元素全部相等的最少操作次数 一样,先按照相同元素将下标分组,然后组内计算贡献(前缀和 + 二分查找)

class Solution {
    public long[] distance(int[] nums) {
        Map<Integer, List<Integer>> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            List<Integer> cur = map.getOrDefault(nums[i], new ArrayList<>());
            cur.add(i);
            map.put(nums[i], cur); //分组以后已经是有序序列了
        }
        long[] res = new long[nums.length];
        for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()){
            List<Integer> cur = entry.getValue();
            int n = cur.size();
            // 预处理前缀和,然后和 2602. 使数组元素全部相等的最少操作次数 一样
            double[] sum = new double[n + 1];
            for(int i = 0; i < n; i++){
                sum[i+1] = sum[i] + cur.get(i);
            }
            // 填入答案数组中
            for(int i = 0; i < n; i++){
                int q = cur.get(i); // q : 在原数组中的下标
                // i 可以看作是比target小的数的个数
                double l = (double)q * i - sum[i];
                double r = sum[n] - sum[i] - (double)q * (n-i);
                res[q] = (long)l + (long)r;
            }
        }   
        return res;
    }
}

相同元素分组+考虑增量

class Solution {
    public long[] distance(int[] nums) {
        Map<Integer, List<Integer>> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            List<Integer> cur = map.getOrDefault(nums[i], new ArrayList<>());
            cur.add(i);
            map.put(nums[i], cur); //分组以后已经是有序序列了
        }
        long[] res = new long[nums.length];
        for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()){
            List<Integer> a = entry.getValue();
            int n = a.size();
            // 先暴力计算出a[0] 到其他元素的距离之和,设为s
            // 然后计算a[1], 不再暴力计算 而是思考 s增加了多少?
            // 从a[0]到a[1],有一个数的距离变大了a[1]-a[0],有n-1个数的距离变小了a[1]-a[0]
            // 对于a[1] ,他到其他元素的距离之和: s + (2-n) * (a[1] - a[0])
            // a[i] : s + (2i - n) * (a[i] - a[i-1])
            double sum = 0.0;
            for(int x : a){
                sum += (double)(x - a.get(0)); // a[0] 到其它下标的距离之和
            }
            res[a.get(0)] = (long)sum;
            for(int i = 1; i < n; i++){
                // 从计算 a[i-1] 到计算 a[i],考虑 s 增加了多少
                sum += (double)(i*2 - n) * (double)(a.get(i) - a.get(i-1));
                res[a.get(i)] = (long)sum;
            }
        }   
        return res;
    }
}

😭6359. 最小化数对的最大差值

难度中等4收藏分享切换为英文接收动态反馈

给你一个下标从 0 开始的整数数组 nums 和一个整数 p 。请你从 nums 中找到 p 个下标对,每个下标对对应数值取差值,你需要使得这 p 个差值的 最大值 最小。同时,你需要确保每个下标在这 p 个下标对中最多出现一次。

对于一个下标对 ij ,这一对的差值为 |nums[i] - nums[j]| ,其中 |x| 表示 x绝对值

请你返回 p 个下标对对应数值 最大差值最小值

示例 1:

输入:nums = [10,1,2,7,1,3], p = 2
输出:1
解释:第一个下标对选择 1 和 4 ,第二个下标对选择 2 和 5 。
最大差值为 max(|nums[1] - nums[4]|, |nums[2] - nums[5]|) = max(0, 1) = 1 。所以我们返回 1 。

示例 2:

输入:nums = [4,2,1,2], p = 1
输出:0
解释:选择下标 1 和 3 构成下标对。差值为 |2 - 2| = 0 ,这是最大差值的最小值。

提示:

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 109
  • 0 <= p <= (nums.length)/2

😰没有check出来!!

二分答案 + 贪心

首先,我们总可以调整贪心策略,使得最后取的每个数对都是在排序数组中连续的(不然可以调整得到结果更好的)。本题“最小化最大”的特点容易提醒我们想到二分。

class Solution {
    public int minimizeMax(int[] nums, int p) {
        Arrays.sort(nums);
        int n = nums.length;
        int left = 0, right = (int)1e9;
        while(left < right){
            int mid = (left + right) >> 1;
            if(!check(mid, nums, p)) left = mid + 1;
            else right = mid;
        }
        return right;
    }

    // 检查【最大差值为m】时,选择尽量多的下标对, 看总数能否大于等于 p 个
	// 贪心:相邻的数相减 差值一定是比较小的
    // i和i+1:
    // 		选 i和i+1(匹配),问题转换成n-2个数的问题
    //    不选 i和i+1(不匹配),不要i,问题转换成n-1个数的问题
    public boolean check(int m, int[] nums, int p){
        int n = nums.length;
        // 最大差值为 mid 时,可取到的下标对数量为 cnt
        int cnt = 0;
        for(int i = 0; i < n-1;){
            // 贪心,判断相邻元素的差值
            if(nums[i+1] - nums[i] <= m){
                cnt++;
                i += 2;
            }else i++;
        }
        return cnt >= p;
    }
}

6353. 网格图中最少访问的格子数

难度困难3收藏分享切换为英文接收动态反馈

给你一个下标从 0 开始的 m x n 整数矩阵 grid 。你一开始的位置在 左上角 格子 (0, 0)

当你在格子 (i, j) 的时候,你可以移动到以下格子之一:

  • 满足 j < k <= grid[i][j] + j 的格子 (i, k) (向右移动),或者
  • 满足 i < k <= grid[i][j] + i 的格子 (k, j) (向下移动)。

请你返回到达 右下角 格子 (m - 1, n - 1) 需要经过的最少移动格子数,如果无法到达右下角格子,请你返回 -1

示例 1:

在这里插入图片描述

输入:grid = [[3,4,2,1],[4,2,3,1],[2,1,0,0],[2,4,0,0]]
输出:4
解释:上图展示了到达右下角格子经过的 4 个格子。

示例 2:

img

输入:grid = [[3,4,2,1],[4,2,1,1],[2,1,1,0],[3,4,1,0]]
输出:3
解释:上图展示了到达右下角格子经过的 3 个格子。

示例 3:

img

输入:grid = [[2,1,0],[1,0,0]]
输出:-1
解释:无法到达右下角格子。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 105
  • 1 <= m * n <= 105
  • 0 <= grid[i][j] < m * n
  • grid[m - 1][n - 1] == 0

看不懂,摆烂

题解答案:https://leetcode.cn/problems/minimum-number-of-visited-cells-in-a-grid/

class Solution {
    /** 
    (0, 0) -> (0, 1) -> (1, 1)
    (0, 0) -> (1, 0) -> (1, 1)
    有重叠子问题 -> 用动态规划来优化
    dfs(0, 0) -> dfs(x, y)

    g = grid[x][y]
    dfs(i, j) 枚举k,计算: min(dfs(i, k), k in [j+1,j+g])
                    计算: min(dfs(k, j), k in [i+1,i+g])
    优化前: O(mn log(m+n))
    
    f[i][j] = min {
                    min(f[i][k], k in [j+1,j+g])
                    min(f[k][j], k in [i+1,i+g])
                }
    i和j倒序枚举

    从右往左计算的时候,如果计算出f[i][j] <= 之前的数,那么之前的数就没用了
    ==> 单调栈    从底往顶,值和下标不断变大的单调栈
    */
    public int minimumVisitedCells(int[][] grid) {
        int m = grid.length, n = grid[0].length, mn = 0;
        List<int[]>[] colSt = new ArrayList[n]; // 每列的单调栈
        Arrays.setAll(colSt, e -> new ArrayList<int[]>());
        for (int i = m - 1; i >= 0; --i) {
            var st = new ArrayList<int[]>(); // 当前行的单调栈
            for (int j = n - 1; j >= 0; --j) {
                var st2 = colSt[j];
                mn = Integer.MAX_VALUE;
                int g = grid[i][j];
                if (i == m - 1 && j == n - 1) // 特殊情况:已经是终点
                    mn = 0;
                else if (g > 0) {
                    // 在单调栈上二分
                    int k = search(st, j + g);
                    if (k < st.size()) mn = Math.min(mn, st.get(k)[0]);
                    k = search(st2, i + g);
                    if (k < st2.size()) mn = Math.min(mn, st2.get(k)[0]);
                }
                if (mn == Integer.MAX_VALUE) continue;

                ++mn; // 加上 (i,j) 这个格子
                // 插入单调栈
                while (!st.isEmpty() && mn <= st.get(st.size() - 1)[0])
                    st.remove(st.size() - 1);
                st.add(new int[]{mn, j});
                while (!st2.isEmpty() && mn <= st2.get(st2.size() - 1)[0])
                    st2.remove(st2.size() - 1);
                st2.add(new int[]{mn, i});
            }
        }
        return mn < Integer.MAX_VALUE ? mn : -1;
    }

    // 见 https://www.bilibili.com/video/BV1AP41137w7/
    private int search(List<int[]> st, int target) {
        int left = -1, right = st.size(); // 开区间 (left, right)
        while (left + 1 < right) { // 区间不为空
            int mid = (left + right) >>> 1;
            if (st.get(mid)[1] > target) left = mid; // 范围缩小到 (mid, right)
            else right = mid; // 范围缩小到 (left, mid)
        }
        return right;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值