周赛376(哈希表+模拟、排序+贪心、中位数贪心+预处理回文数+二分、中位数贪心+前缀和)

周赛376

2965. 找出缺失和重复的数字

简单

给你一个下标从 0 开始的二维整数矩阵 grid,大小为 n * n ,其中的值在 [1, n2] 范围内。除了 a 出现 两次b 缺失 之外,每个整数都 恰好出现一次

任务是找出重复的数字a 和缺失的数字 b

返回一个下标从 0 开始、长度为 2 的整数数组 ans ,其中 ans[0] 等于 aans[1] 等于 b

示例 1:

输入:grid = [[1,3],[2,2]]
输出:[2,4]
解释:数字 2 重复,数字 4 缺失,所以答案是 [2,4] 。

示例 2:

输入:grid = [[9,1,7],[8,9,2],[3,4,6]]
输出:[9,5]
解释:数字 9 重复,数字 5 缺失,所以答案是 [9,5] 。

提示:

  • 2 <= n == grid.length == grid[i].length <= 50
  • 1 <= grid[i][j] <= n * n
  • 对于所有满足1 <= x <= n * nx ,恰好存在一个 x 与矩阵中的任何成员都不相等。
  • 对于所有满足1 <= x <= n * nx ,恰好存在一个 x 与矩阵中的两个成员相等。
  • 除上述的两个之外,对于所有满足1 <= x <= n * nx ,都恰好存在一对 i, j 满足 0 <= i, j <= n - 1grid[i][j] == x

哈希表 + 模拟

class Solution {
    public int[] findMissingAndRepeatedValues(int[][] grid) {
        int n = grid.length;
        int[] cnt = new int[n * n + 1];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cnt[grid[i][j]]++;
            }
        }
        int d = 0, los = 0;
        for(int i = 1; i < n*n+1; i++){
            if(cnt[i] == 0)
                los = i;
            if(cnt[i] > 1)
                d = i;
        }
        return new int[]{d, los};
    }
}	

2966. 划分数组并满足最大差限制

中等

给你一个长度为 n 的整数数组 nums,以及一个正整数 k

将这个数组划分为一个或多个长度为 3 的子数组,并满足以下条件:

  • nums 中的 每个 元素都必须 恰好 存在于某个子数组中。
  • 子数组中 任意 两个元素的差必须小于或等于 k

返回一个 二维数组 ,包含所有的子数组。如果不可能满足条件,就返回一个空数组。如果有多个答案,返回 任意一个 即可。

示例 1:

输入:nums = [1,3,4,8,7,9,3,5,1], k = 2
输出:[[1,1,3],[3,4,5],[7,8,9]]
解释:可以将数组划分为以下子数组:[1,1,3],[3,4,5] 和 [7,8,9] 。
每个子数组中任意两个元素的差都小于或等于 2 。
注意,元素的顺序并不重要。

示例 2:

输入:nums = [1,3,3,2,7,3], k = 3
输出:[]
解释:无法划分数组满足所有条件。

提示:

  • n == nums.length
  • 1 <= n <= 105
  • n3 的倍数
  • 1 <= nums[i] <= 105
  • 1 <= k <= 105

排序 + 贪心

class Solution {
    /**
    不要求顺序,先排序
    每个元素都要在某个子数组中,即分为 n/3个子数组(n 是 3 的倍数)
    	满足a[i+2] - a[i] <= k
    */
    public int[][] divideArray(int[] nums, int k) {
        List<int[]> res = new ArrayList<>();
        Arrays.sort(nums);
        int n = nums.length;
        for(int i = 0; i < n; i += 3){
            if(nums[i+2] - nums[i] > k) 
                return new int[][]{};
            res.add(new int[]{nums[i], nums[i+1], nums[i+2]});
        }  
        return res.toArray(new int[0][]);
    }
}s

2967. 使数组成为等数数组的最小代价

中等

给你一个长度为 n 下标从 0 开始的整数数组 nums

你可以对 nums 执行特殊操作 任意次 (也可以 0 次)。每一次特殊操作中,你需要 按顺序 执行以下步骤:

  • 从范围 [0, n - 1] 里选择一个下标 i 和一个 整数 x
  • |nums[i] - x| 添加到总代价里。
  • nums[i] 变为 x

如果一个正整数正着读和反着读都相同,那么我们称这个数是 回文数 。比方说,121255265756 都是回文数,但是 2446235 都不是回文数。

如果一个数组中的所有元素都等于一个整数 y ,且 y 是一个小于 109回文数 ,那么我们称这个数组是一个 等数数组

请你返回一个整数,表示执行任意次特殊操作后使 nums 成为 等数数组最小 总代价。

示例 1:

输入:nums = [1,2,3,4,5]
输出:6
解释:我们可以将数组中所有元素变为回文数 3 得到等数数组,数组变成 [3,3,3,3,3] 需要执行 4 次特殊操作,代价为 |1 - 3| + |2 - 3| + |4 - 3| + |5 - 3| = 6 。
将所有元素变为其他回文数的总代价都大于 6 。

示例 2:

输入:nums = [10,12,13,14,15]
输出:11
解释:我们可以将数组中所有元素变为回文数 11 得到等数数组,数组变成 [11,11,11,11,11] 需要执行 5 次特殊操作,代价为 |10 - 11| + |12 - 11| + |13 - 11| + |14 - 11| + |15 - 11| = 11 。
将所有元素变为其他回文数的总代价都大于 11 。

示例 3 :

输入:nums = [22,33,22,33,22]
输出:22
解释:我们可以将数组中所有元素变为回文数 22 得到等数数组,数组变为 [22,22,22,22,22] 需要执行 2 次特殊操作,代价为 |33 - 22| + |33 - 22| = 22 。
将所有元素变为其他回文数的总代价都大于 22 。

提示:

  • 1 <= n <= 105
  • 1 <= nums[i] <= 109

预处理回文数 + 中位数贪心

https://leetcode.cn/problems/minimum-cost-to-make-array-equalindromic/solutions/2569308/yu-chu-li-hui-wen-shu-zhong-wei-shu-tan-7j0zy/

class Solution {
    /**
    中位数贪心
    定理:将 a 的所有元素变为 a 的中位数是最优的
    如何理解? 考虑变为a[0]和a[n-1]的中间位置x,a[0]移动b步,a[n-1]移动c步,总共b+c步
                当变为x+1时,a[0]移动b+1步,a[n-1]移动c-1步,总共b+c步
              因此变为a[0]和a[n-1]中间节点时,a[0]+a[n-1]总的步数时相同的
    本题要求变的数字一定要是回文数,因此可以找离中位数最近的数
    问题1:如何预处理回文数?「10^9大约有109998个回文数」
        通过枚举回文数的左半部分得到
        注意每个i对应两个回文数,一个奇数一个偶数
     */
    private static final int[] pal = new int[109999];
    static{
        // 严格按顺序从小到大生成所有回文数(不用字符串转换)
        int palIdx = 0;
        for(int base = 1; base <= 10000; base *= 10){ // 枚举回文数的长度
            // 生成奇数长度回文数
            for(int i = base; i < base * 10; i++){
                int x = i; // 123 .. 123*10+2 .. 1232*10+1 ==> 12321 
                for(int t = i / 10; t > 0; t /= 10){
                    x = x * 10 + t % 10;
                }
                pal[palIdx++] = x;
            }
            // 生成偶数长度回文数
            if(base <= 1000){
                for(int i = base; i < base * 10; i++){
                    int x = i;
                    for(int t = i; t > 0; t /= 10){ // 区别在于t初始是否/10
                        x = x * 10 + t % 10;
                    }
                    pal[palIdx++] = x;
                }
            }
        }
        pal[palIdx++] = (int)1e9+1; // 哨兵,防止下面代码中的 i 下标越界
    }
    public long minimumCost(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        // 答案中位数可能存在与 nums[n/2] 和 num[(n-1)/2] 之间的任意回文数中
        // 通过二分找中位数的左端点,然后枚举所有小于num[n/2]的回文数
        int i = lowerBound(nums[(n-1)/2]);
        long res = 0;
        if(pal[i] <= nums[n/2]){
            // 回文数在中位数范围之内
            return cost(nums, i); // 直接变成pai[i]
        }
        // 枚举离中位数最近的两个回文数 pal[i-1] 和 pal[i]
        return Math.min(cost(nums, i-1), cost(nums, i));
    }
    // 返回 nums 中的所有数变成 pal[i] 的总代价
    private long cost(int[] nums, int i){
        int target = pal[i];
        long sum = 0;
        for(int x : nums){
            sum += Math.abs(x - target);
        }
        return sum;
    }

    // 二分找到第一个 <= key 的元素下标
    public int lowerBound(int target){
        int left = 0, right = pal.length;
        while(left < right){
            int mid = (left + right) >> 1;
            if(pal[mid] < target) left = mid + 1;
            else right = mid;
        }
        return right;
    }
}

思考题:能否直接找离某个数最近的回文数?

这题是 564. 寻找最近的回文数,注意数据范围。

中位数贪心题单

预处理回文数的题

2968. 执行操作使频率分数最大

困难

给你一个下标从 0 开始的整数数组 nums 和一个整数 k

你可以对数组执行 至多 k 次操作:

  • 从数组中选择一个下标 i ,将 nums[i] 增加 或者 减少 1

最终数组的频率分数定义为数组中众数的 频率

请你返回你可以得到的 最大 频率分数。

众数指的是数组中出现次数最多的数。一个元素的频率指的是数组中这个元素的出现次数。

示例 1:

输入:nums = [1,2,6,4], k = 3
输出:3
解释:我们可以对数组执行以下操作:
- 选择 i = 0 ,将 nums[0] 增加 1 。得到数组 [2,2,6,4] 。
- 选择 i = 3 ,将 nums[3] 减少 1 ,得到数组 [2,2,6,3] 。
- 选择 i = 3 ,将 nums[3] 减少 1 ,得到数组 [2,2,6,2] 。
元素 2 是最终数组中的众数,出现了 3 次,所以频率分数为 3 。
3 是所有可行方案里的最大频率分数。

示例 2:

输入:nums = [1,4,4,2,4], k = 0
输出:3
解释:我们无法执行任何操作,所以得到的频率分数是原数组中众数的频率 3 。

提示:

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

中位数贪心 + 前缀和

https://leetcode.cn/problems/apply-operations-to-maximize-frequency-score/solutions/2569301/hua-dong-chuang-kou-zhong-wei-shu-tan-xi-nuvr/

class Solution {
    /**
    排序后,要变成一样的数必然在一个连续子数组中,可以使用滑动窗口来做,枚举右端点,维护左端点
    根据中位数贪心,最有做法是把子数组内的元素变为子数组的中位数,如果操作次数超过k,则必须要移动左端点
     */
    public int maxFrequencyScore(int[] nums, long k) {
        Arrays.sort(nums);
        int n = nums.length;
        long[] s = new long[n+1];
        for(int i = 0; i < n; i++)
            s[i+1] = s[i] + nums[i];

        int ans = 0, left = 0;
        for(int right = 0; right < n; right++){
            // 如果把子数组内的元素变为子数组的中位数 操作次数超过k,则必须要移动左端点
            while(distanceSum(s, nums, left, (left+right)/2, right) > k){
                left++;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }

    // https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/solutions/2191417/yi-tu-miao-dong-pai-xu-qian-zhui-he-er-f-nf55/
    // 将 子数组内的元素变为nums[mid]所需要的操作次数
    public long distanceSum(long[] s, int[] nums, int left, int mid, int right){
        long leftSum = (long) nums[mid] * (mid - left) - (s[mid] - s[left]);
        long rightSum = s[right+1] - s[mid+1] - (long) nums[mid] * (right - mid);
        return leftSum + rightSum;
    }
}
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值