340场周赛

对角线上的质数

image-20230415092227417

示例 1:

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

示例 2:

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

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int diagonalPrime(int[][] nums) {
int max = 0;
for (int i = 0;i<nums.length;i++){
//两条对角线
max = Math.max(max,isOk(nums[i][i]));
max = Math.max(max,isOk(nums[i][nums.length-1-i]));
}
return max;
}
//判断是不是质数
public int isOk(int n){
if(n<=1) return 0;
for (int i = 2;i*i<=n;i++){
if (n%i==0) return 0;
}
return n;
}
}

等值距离和

image-20230414210831486

示例 1:

1
2
3
4
5
6
7
8
输入: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:

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

+++++++++++++

思路:这题可以使用前缀和+二分法的方式进行解答

这题的意思就是求每个数的下标到与它想等数的下标距离之和。

例如说:示例1中,值为1的下标为0,与它相等值得下标为2和3,因此就是求0到2和3的距离之和。

在做这道题之前先做一下力扣周赛的2602题,和这题使用相同的方法。

先来看看这道题吧:

image-20230414211238717

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
输入:nums = [3,1,6,8], queries = [1,5]
输出:[14,10]
解释:第一个查询,我们可以执行以下操作:

- 将 nums[0] 减小 2 次,nums = [1,1,6,8]
- 将 nums[2] 减小 5 次,nums = [1,1,1,8]
- 将 nums[3] 减小 7 次,nums = [1,1,1,1]
第一个查询的总操作次数为 2 + 5 + 7 = 14 。
第二个查询,我们可以执行以下操作:
- 将 nums[0] 增大 2 次,nums = [5,1,6,8]
- 将 nums[1] 增大 4 次,nums = [5,5,6,8]
- 将 nums[2] 减小 1 次,nums = [5,5,5,8]
- 将 nums[3] 减小 3 次,nums = [5,5,5,5]
第二个查询的总操作次数为 2 + 4 + 1 + 3 = 10

示例 2:

1
2
3
输入:nums = [2,9,6,3], queries = [10]
输出:[20]
解释:我们可以将数组中所有元素都增大到 10 ,总操作次数为 8 + 1 + 4 + 7 = 20

这个就是求queriise数组中的数到nums数组中每一个数的距离和。

image-20230414211445728

首先将num排序,也就是图中的柱状体,图中的q就是querise中的数。那么距离就等于蓝色面积+绿色面积。为了简化计算过程,加入了前缀和来方便计算面积。

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public List<Long> minOperations(int[] nums, int[] queries) {
Arrays.sort(nums);
int n = nums.length;
long sum[] = new long[n+1];
//前缀和
for (int i = 1;i<n+1;i++){
sum[i] = sum[i-1] + nums[i-1];
}
List<Long> list = new ArrayList<>();
for (int i = 0;i<queries.length;i++){
int a = queries[i];
int p = search(nums,a);
//蓝色面积
long left = (long)a*p - sum[p];
//绿色面积
long right = sum[n]-sum[p]-(n-p)*(long)a;
list.add(left+right);
}
return list;
}
//二分法,找到蓝色面积和绿色面积的交界处
public int search(int nums[],int a){
int l =0;
int r = nums.length;
while (l<r){
int mid = (l+r)>>1;
if (nums[mid]>=a){
r = mid;
}else {
l = mid +1;
}
}
return r;
}
}

以上这题会做了后,那这道题的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public long[] distance(int[] nums) {
HashMap<Integer,List<Integer>> map = new HashMap<>();
int n = nums.length;
for (int i = 0;i<n;i++){
//判断map的key中是否有nums[i],如果有则直接返回value,否则创建value的类型。之后添加i。
map.computeIfAbsent(nums[i],(k)->new ArrayList<>()).add(i);
}
long res[] = new long[n];
for (List<Integer> list:map.values()){
int size = list.size();
long sum[] = new long[size+1];
//前缀和
for (int i = 1;i<=size;i++){
sum[i] = sum[i-1] + list.get(i-1);
}
//不用二分法求下标,因为本身就知道a在list中的下标为i。
for (int i = 0;i<size;i++){
int a = list.get(i);
long left = i*(long)a-sum[i];
long right = sum[size]-sum[i]-(long)a*(size-i);
res[a] = left + right;
}
}
return res;
}
}

最小化数对的最大差值

image-20230415092322706

示例 1:

1
2
3
4
输入: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:

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

+++++++++++

思路:

看到「最大化最小值」或者「最小化最大值」就要想到二分答案,这是一个固定的套路。

这题可以参考打家劫舍 IV

下面先看打家劫舍这道题怎么做:

image-20230415124904950

示例 1:

1
2
3
4
5
6
7
8
9
输入:nums = [2,3,5,9], k = 2
输出:5
解释:
小偷窃取至少 2 间房屋,共有 3 种方式:

- 窃取下标 0 2 处的房屋,窃取能力为 max(nums[0], nums[2]) = 5
- 窃取下标 0 3 处的房屋,窃取能力为 max(nums[0], nums[3]) = 9
- 窃取下标 1 3 处的房屋,窃取能力为 max(nums[1], nums[3]) = 9
因此,返回 min(5, 9, 9) = 5

示例 2:

1
2
3
输入:nums = [2,7,9,3,1], k = 2
输出:2
解释:共有 7 种窃取方式。窃取能力最小的情况所对应的方式是窃取下标 04 处的房屋。返回 max(nums[0], nums[4]) = 2

思路:二分法 + dp

二分法:通过二分法寻找可能出现的结果,保证所偷取的房间数>=k。

dp:计算通过二分法猜想的结果,可以偷取多少个房间。

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public int minCapability(int[] nums, int k) {
int n = nums.length;
int l = 1;
int r = Arrays.stream(nums).max().getAsInt();
//dp[i]:表示前i个房间可以偷取的最大房间数目,可以参考打家劫舍-198
int dp[] = new int[n+1];
while (l<r){
int mid = (l+r)>>1;
if (nums[0]<=mid) {
dp[1] = 1;
}else dp[1] = 0;
for (int i = 2;i<=nums.length;i++){
if (nums[i-1]>mid){
dp[i] = dp[i-1];
}else {
dp[i] = Math.max(dp[i-2]+1,dp[i-1]);
}
}
//如果当前mid值满足k,则小于mid的值也一定满足,所以从mid前面找
if (dp[n]>=k){
r = mid;
}else {
l = mid+1;
}
}
return r;
}
}

优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public static int minCapability(int[] nums, int k) {
int n = nums.length;
int l = 1;
int r = Arrays.stream(nums).max().getAsInt();
while (l<r){
int mid = (l+r)>>1;
int a = 0;
int b = nums[0]<=mid?1:0;
int c = 0;
for (int i = 2;i<=nums.length;i++){
if (nums[i-1]>mid){
c = b;
}else {
c = Math.max(a+1,b);
}
a = b;
b = c;
}
if (c>=k){
r = mid;
}else {
l = mid+1;
}
}
return l;
}
}

这道题与上一道题的解法相同:

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int minimizeMax(int[] nums, int p) {
Arrays.sort(nums);
int n = nums.length;
int l = 0;
int r = nums[n-1];
//dp[i]:表示前i个数,都多少对小于mid。
int dp[] = new int[n+1];
while (l<r){
int mid =(l+r)>>1;
for (int i = 0;i<nums.length-1;i++){
if (nums[i+1]-nums[i]<=mid){
dp[i+2] = dp[i] + 1;
}else {
dp[i+2] = dp[i+1];
}
}
if (dp[n]>=p){
r = mid;
}else {
l = mid + 1;
}
}
return l;
}
}

以上动态规划也可以优化为贪心做法:

image-20230415133235448

优化后的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int minimizeMax(int[] nums, int p) {
Arrays.sort(nums);
int n = nums.length;
int l = 0;
int r = nums[n-1];
while (l<r){
int mid =(l+r)>>1;
int count = 0;
for (int i = 0;i<nums.length-1;i++){
//如果能选直接选,i后移两位。
if (nums[i+1]-nums[i]<=mid){
count++;
i++;
}
}
if (count>=p){
r = mid;
}else {
l = mid + 1;
}
}
return l;
}
}

340场周赛
http://example.com/2023/04/14/340场周赛/
作者
zlw
发布于
2023年4月14日
许可协议