文章目录
- 周赛376
- [2965. 找出缺失和重复的数字](https://leetcode.cn/problems/find-missing-and-repeated-values/)
- [2966. 划分数组并满足最大差限制](https://leetcode.cn/problems/divide-array-into-arrays-with-max-difference/)
- [2967. 使数组成为等数数组的最小代价](https://leetcode.cn/problems/minimum-cost-to-make-array-equalindromic/)
- [2968. 执行操作使频率分数最大](https://leetcode.cn/problems/apply-operations-to-maximize-frequency-score/)
周赛376
2965. 找出缺失和重复的数字
简单
给你一个下标从 0 开始的二维整数矩阵 grid
,大小为 n * n
,其中的值在 [1, n2]
范围内。除了 a
出现 两次,b
缺失 之外,每个整数都 恰好出现一次 。
任务是找出重复的数字a
和缺失的数字 b
。
返回一个下标从 0 开始、长度为 2
的整数数组 ans
,其中 ans[0]
等于 a
,ans[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 * n
的x
,恰好存在一个x
与矩阵中的任何成员都不相等。 - 对于所有满足
1 <= x <= n * n
的x
,恰好存在一个x
与矩阵中的两个成员相等。 - 除上述的两个之外,对于所有满足
1 <= x <= n * n
的x
,都恰好存在一对i, j
满足0 <= i, j <= n - 1
且grid[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
n
是3
的倍数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
。
如果一个正整数正着读和反着读都相同,那么我们称这个数是 回文数 。比方说,121
,2552
和 65756
都是回文数,但是 24
,46
,235
都不是回文数。
如果一个数组中的所有元素都等于一个整数 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. 寻找最近的回文数,注意数据范围。
中位数贪心题单
-
2033. 获取单值网格的最小操作数 1672
-
2448. 使数组相等的最小开销 2005
-
2607. 使子数组元素和相等 2071
预处理回文数的题
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;
}
}