3180.执行操作可获得的最大总奖励I

目标

给你一个整数数组 rewardValues,长度为 n,代表奖励的值。

最初,你的总奖励 x 为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :

  • 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。
  • 如果 rewardValues[i] 大于 你当前的总奖励 x,则将 rewardValues[i] 加到 x 上(即 x = x + rewardValues[i]),并 标记 下标 i。

以整数形式返回执行最优操作能够获得的 最大 总奖励。

示例 1:

输入:rewardValues = [1,1,3,3]
输出:4
解释:
依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。

示例 2:

输入:rewardValues = [1,6,4,3,2]
输出:11
解释:
依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。

说明:

  • 1 <= rewardValues.length <= 2000
  • 1 <= rewardValues[i] <= 2000

思路

有一个长度为 n 的整数数组 rewardValues 和一个变量 x 初始为 0,可以执行任意次操作,每次操作可以从 [0, n - 1] 中选一个未标记的下标 i,如果 rewardValues[i] > xx += rewardValues[i],并标记 i。求 x 的最大值。

// todo

代码

性能

3175.找到连续赢K场比赛的第一位玩家

目标

有 n 位玩家在进行比赛,玩家编号依次为 0 到 n - 1 。

给你一个长度为 n 的整数数组 skills 和一个 整数 k ,其中 skills[i] 是第 i 位玩家的技能等级。skills 中所有整数 互不相同 。

所有玩家从编号 0 到 n - 1 排成一列。

比赛进行方式如下:

  • 队列中最前面两名玩家进行一场比赛,技能等级 更高 的玩家胜出。
  • 比赛后,获胜者保持在队列的开头,而失败者排到队列的末尾。

这个比赛的赢家是 第一位连续 赢下 k 场比赛的玩家。

请你返回这个比赛的赢家编号。

示例 1:

输入:skills = [4,2,6,3,9], k = 2
输出:2
解释:
一开始,队列里的玩家为 [0,1,2,3,4] 。比赛过程如下:
玩家 0 和 1 进行一场比赛,玩家 0 的技能等级高于玩家 1 ,玩家 0 胜出,队列变为 [0,2,3,4,1] 。
玩家 0 和 2 进行一场比赛,玩家 2 的技能等级高于玩家 0 ,玩家 2 胜出,队列变为 [2,3,4,1,0] 。
玩家 2 和 3 进行一场比赛,玩家 2 的技能等级高于玩家 3 ,玩家 2 胜出,队列变为 [2,4,1,0,3] 。
玩家 2 连续赢了 k = 2 场比赛,所以赢家是玩家 2 。

示例 2:

输入:skills = [2,5,4], k = 3
输出:1
解释:
一开始,队列里的玩家为 [0,1,2] 。比赛过程如下:
玩家 0 和 1 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为 [1,2,0] 。
玩家 1 和 2 进行一场比赛,玩家 1 的技能等级高于玩家 2 ,玩家 1 胜出,队列变为 [1,0,2] 。
玩家 1 和 0 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为 [1,2,0] 。
玩家 1 连续赢了 k = 3 场比赛,所以赢家是玩家 1 。

说明:

  • n == skills.length
  • 2 <= n <= 10^5
  • 1 <= k <= 10^9
  • 1 <= skills[i] <= 10^6
  • skills 中的整数互不相同。

思路

n 个玩家排成一队并从 0n - 1 编号,有一个元素互不相同的 skills 数组,skills[i] 代表玩家 i 的技能等级,队首的两位玩家进行比赛,技能等级高的胜出,输的排队尾,胜出的玩家接着与队首的玩家比赛,如此循环,问连续赢得 k 场比赛的玩家编号。

由于技能等级互不相同,一定有解,直接按题意模拟即可。但是考虑到 k 最大为 10^9,如果真循环模拟比赛 k 次也会超时。其实只要开始没有出现连胜 k 次的玩家,最后的胜者一定是技能等级最高的,那么后续的比赛一定全胜,直接返回即可。

代码


/**
 * @date 2024-10-24 0:41
 */
public class FindWinningPlayer3175 {

    public int findWinningPlayer(int[] skills, int k) {
        int n = skills.length;
        int cnt = 0;
        int res = 0;
        for (int i = 1; i < n; i++) {
            if (cnt == k) {
                return res;
            }
            if (skills[res] > skills[i]) {
                cnt++;
            } else {
                cnt = 1;
                res = i;
            }
        }
        return res;
    }

}

性能

3175.找到连续赢K场比赛的第一位玩家

目标

有 n 位玩家在进行比赛,玩家编号依次为 0 到 n - 1 。

给你一个长度为 n 的整数数组 skills 和一个 整数 k ,其中 skills[i] 是第 i 位玩家的技能等级。skills 中所有整数 互不相同 。

所有玩家从编号 0 到 n - 1 排成一列。

比赛进行方式如下:

  • 队列中最前面两名玩家进行一场比赛,技能等级 更高 的玩家胜出。
  • 比赛后,获胜者保持在队列的开头,而失败者排到队列的末尾。

这个比赛的赢家是 第一位连续 赢下 k 场比赛的玩家。

请你返回这个比赛的赢家编号。

示例 1:

输入:skills = [4,2,6,3,9], k = 2
输出:2
解释:
一开始,队列里的玩家为 [0,1,2,3,4] 。比赛过程如下:
玩家 0 和 1 进行一场比赛,玩家 0 的技能等级高于玩家 1 ,玩家 0 胜出,队列变为 [0,2,3,4,1] 。
玩家 0 和 2 进行一场比赛,玩家 2 的技能等级高于玩家 0 ,玩家 2 胜出,队列变为 [2,3,4,1,0] 。
玩家 2 和 3 进行一场比赛,玩家 2 的技能等级高于玩家 3 ,玩家 2 胜出,队列变为 [2,4,1,0,3] 。
玩家 2 连续赢了 k = 2 场比赛,所以赢家是玩家 2 。

示例 2:

输入:skills = [2,5,4], k = 3
输出:1
解释:
一开始,队列里的玩家为 [0,1,2] 。比赛过程如下:
玩家 0 和 1 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为 [1,2,0] 。
玩家 1 和 2 进行一场比赛,玩家 1 的技能等级高于玩家 2 ,玩家 1 胜出,队列变为 [1,0,2] 。
玩家 1 和 0 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为 [1,2,0] 。
玩家 1 连续赢了 k = 3 场比赛,所以赢家是玩家 1 。

说明:

  • n == skills.length
  • 2 <= n <= 10^5
  • 1 <= k <= 10^9
  • 1 <= skills[i] <= 10^6
  • skills 中的整数互不相同。

思路

n 个玩家排成一队并从 0n - 1 编号,有一个元素互不相同的 skills 数组,skills[i] 代表玩家 i 的技能等级,队首的两位玩家进行比赛,技能等级高的胜出,输的排队尾,胜出的玩家接着与队首的玩家比赛,如此循环,问连续赢得 k 场比赛的玩家编号。

由于技能等级互不相同,一定有解,直接按题意模拟即可。但是考虑到 k 最大为 10^9,如果真循环模拟比赛 k 次也会超时。其实只要开始没有出现连胜 k 次的玩家,最后的胜者一定是技能等级最高的,那么后续的比赛一定全胜,直接返回即可。

代码


/**
 * @date 2024-10-24 0:41
 */
public class FindWinningPlayer3175 {

    public int findWinningPlayer(int[] skills, int k) {
        int n = skills.length;
        int cnt = 0;
        int res = 0;
        for (int i = 1; i < n; i++) {
            if (cnt == k) {
                return res;
            }
            if (skills[res] > skills[i]) {
                cnt++;
            } else {
                cnt = 1;
                res = i;
            }
        }
        return res;
    }

}

性能

3185.构成整天的下标对数目II

目标

给你一个整数数组 hours,表示以 小时 为单位的时间,返回一个整数,表示满足 i < j 且 hours[i] + hours[j] 构成 整天 的下标对 i, j 的数目。

整天 定义为时间持续时间是 24 小时的 整数倍 。

例如,1 天是 24 小时,2 天是 48 小时,3 天是 72 小时,以此类推。

示例 1:

输入: hours = [12,12,30,24,24]
输出: 2
解释:
构成整天的下标对分别是 (0, 1) 和 (3, 4)。

示例 2:

输入: hours = [72,48,24,3]
输出: 3
解释:
构成整天的下标对分别是 (0, 1)、(0, 2) 和 (1, 2)。

说明:

  • 1 <= hours.length <= 5 * 10^5
  • 1 <= hours[i] <= 10^9

思路

有一个整数数组 hours,返回 hours[i] + hours[j] % 24 == 0i < j 的下标对的个数。

与昨天的题目 3184.构成整天的下标对数目I 相比,数据规模从 100 变成了 5 * 10^5。枚举下标对的时间复杂度为 O(C(n,2)),即 O(n^2),肯定会超时。

题目并不要求输出具体的下标对,只需要计数即可。当枚举右端点时,如果可以直接获取到已访问过的元素中,能够与当前元素组成合法下标对的元素个数,那么整体的时间复杂度可以降为 O(n)。定义 cnt[m] 表示已访问过的元素中对 24 取余后值为 m 的元素个数。当枚举到元素 i 时,只需累加 cnt[24 - nums[i] % 24] 即可。

代码


/**
 * @date 2024-10-23 10:16
 */
public class CountCompleteDayPairs3185 {

    public long countCompleteDayPairs_v2(int[] hours) {
        long res = 0L;
        int[] cnt = new int[24];
        int zeroCnt = 0;
        for (int hour : hours) {
            int m = hour % 24;
            if (m == 0) {
                res += zeroCnt;
                zeroCnt++;
            } else {
                res += cnt[24 - m];
                cnt[m]++;
            }
        }
        return res;
    }

}

性能

910.最小差值II

目标

给你一个整数数组 nums,和一个整数 k 。

对于每个下标 i(0 <= i < nums.length),将 nums[i] 变成 nums[i] + k 或 nums[i] - k 。

nums 的 分数 是 nums 中最大元素和最小元素的差值。

在更改每个下标对应的值之后,返回 nums 的最小 分数 。

示例 1:

输入:nums = [1], k = 0
输出:0
解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。

示例 2:

输入:nums = [0,10], k = 2
输出:6
解释:将数组变为 [2, 8] 。分数 = max(nums) - min(nums) = 8 - 2 = 6 。

示例 3:

输入:nums = [1,3,6], k = 3
输出:3
解释:将数组变为 [4, 6, 3] 。分数 = max(nums) - min(nums) = 6 - 3 = 3 。

说明:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 10^4
  • 0 <= k <= 10^4

思路

这道题与 908.最小差值I 的区别是对于每个元素操作是 必选的,增加或减少的值 固定k 而不是区间 [-k, k]

每个元素都可以取 nums[i] + knums[i] - k,如果枚举所有可能的数组,然后再取最大最小值差的最小值显然是不可能的。不考虑重复元素的情况下,可能的数组有 2^n 种。

可以先排序,增大小的值,减少大的值,枚举二者的边界,比如前 i 个元素 [0, i - 1]k,剩余元素减 k,这时上界为 Math.max(nums[i - 1] + k, nums[n - 1] - k) ,下界为 Math.min(nums[0] + k, nums[i] - k),取上下界的最小值即可。

代码


/**
 * @date 2024-10-21 9:57
 */
public class SmallestRangeII910 {

    public int smallestRangeII(int[] nums, int k) {
        Arrays.sort(nums);
        int n = nums.length;
        int res = nums[n - 1] - nums[0];
        for (int i = 1; i < n; i++) {
            int max = Math.max(nums[i - 1] + k, nums[n - 1] - k);
            int min = Math.min(nums[0] + k, nums[i] - k);
            res = Math.min(res, max - min);
        }
        return res;
    }

}

性能

3192.使二进制数组全部等于1的最少操作次数II

目标

给你一个二进制数组 nums 。

你可以对数组执行以下操作 任意 次(也可以 0 次):

  • 选择数组中 任意 一个下标 i ,并将从下标 i 开始一直到数组末尾 所有 元素 反转 。

反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。

请你返回将 nums 中所有元素变为 1 的 最少 操作次数。

示例 1:

输入:nums = [0,1,1,0,1]
输出:4
解释:
我们可以执行以下操作:
选择下标 i = 1 执行操作,得到 nums = [0,0,0,1,0] 。
选择下标 i = 0 执行操作,得到 nums = [1,1,1,0,1] 。
选择下标 i = 4 执行操作,得到 nums = [1,1,1,0,0] 。
选择下标 i = 3 执行操作,得到 nums = [1,1,1,1,1] 。

示例 2:

输入:nums = [1,0,0,0]
输出:1
解释:
我们可以执行以下操作:
选择下标 i = 1 执行操作,得到 nums = [1,1,1,1] 。

说明:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 1

思路

有一个二进制数组 nums,每一次操作可以将当前元素以及之后的元素反转,问将所有元素变为 1 的最少操作次数。

这与昨天的题目 3191.使二进制数组全部等于1的最少操作次数I 类似,那个是将当前元素及后面两个元素反转。

还沿用昨天的思路,遇到 0 就进行操作,每一次操作需要对大量元素进行取模运算,因此考虑使用差分数组。这里差分数组初始均为 0,每次操作是针对当前元素及以后的所有元素,无需考虑后续的减法操作,因此只需要一个变量计数即可。

网友最快的算法并非使用变量计数然后判断奇偶性,考虑到最终目标是将所有元素都变为 1,每一次操作会将自身与其后的元素反转,对于初始数组任意位置 i,如果它为 0,该位置一定需要执行奇数次操作,因为执行偶数次反转最后还是 0。同理,如果为 1,需要执行 偶数 次操作。实际上每个位置是否需要执行操作是 确定的

只要有一个初始条件,向后遍历的时候直接根据前一个元素与当前元素的关系判断是否需要累加操作即可,具体来说,如果它们值相同则不需要操作,如果不同则需要操作,直接累加这两个元素的异或值即可。

代码


/**
 * @date 2024-10-19 17:30
 */
public class MinOperations3192 {

    public int minOperations_v2(int[] nums) {
        int n = nums.length;
        int prev = nums[0];
        int res = prev ^ 1;
        for (int i = 1; i < n; i++) {
            res += prev ^ nums[i];
            prev = nums[i];
        }
        return res;
    }

    public int minOperations_v1(int[] nums) {
        int n = nums.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (((res % 2 ^ nums[i]) == 0)) {
                res++;
            }
        }
        return res;
    }

}

性能

3191.使二进制数组全部等于1的最少操作次数I

目标

给你一个二进制数组 nums 。

你可以对数组执行以下操作 任意 次(也可以 0 次):

  • 选择数组中 任意连续 3 个元素,并将它们 全部反转 。

反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。

请你返回将 nums 中所有元素变为 1 的 最少 操作次数。如果无法全部变成 1 ,返回 -1 。

示例 1:

输入:nums = [0,1,1,1,0,0]
输出:3
解释:
我们可以执行以下操作:
选择下标为 0 ,1 和 2 的元素并反转,得到 nums = [1,0,0,1,0,0] 。
选择下标为 1 ,2 和 3 的元素并反转,得到 nums = [1,1,1,0,0,0] 。
选择下标为 3 ,4 和 5 的元素并反转,得到 nums = [1,1,1,1,1,1] 。

示例 2:

输入:nums = [0,1,1,1]
输出:-1
解释:
无法将所有元素都变为 1 。

说明:

  • 3 <= nums.length <= 10^5
  • 0 <= nums[i] <= 1

思路

有一个二进制数组 nums,每一次操作可以将其中连续的三个元素取反,求将其中的所有元素置为 1 的最少操作次数,如果无法将所有元素都变为 1,返回 -1

直觉告诉我 遇到 0 就执行一次操作 就是最少的操作次数。

如果 nums[0] == 0,将它变成 1唯一 方法是对其进行操作,即反转 nums[0] nums[1] nums[2]。将同样的逻辑应用于接下来的 1 ~ n - 1,换句话说 结果是注定的最少操作方式也是唯一的

代码


/**
 * @date 2024-10-18 8:53
 */
public class MinOperations3191 {

    public int minOperations_v1(int[] nums) {
        int res = 0;
        int n = nums.length;
        for (int i = 0; i < n - 2; i++) {
            if (nums[i] == 0) {
                nums[i + 1] ^= 1;
                nums[i + 2] ^= 1;
                res++;
            }
        }
        return (nums[n - 1] == 0 || nums[n - 2] == 0) ? -1 : res;
    }

}

性能

887.鸡蛋掉落

目标

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。

每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。

请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

示例 1:

输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。 
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。 
如果它没碎,那么肯定能得出 f = 2 。 
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 

示例 2:

输入:k = 2, n = 6
输出:3

示例 3:

输入:k = 3, n = 14
输出:4

提示:

  • 1 <= k <= 100
  • 1 <= n <= 10^4

思路

参考1884.鸡蛋掉落-两枚鸡蛋,这次是 k 枚鸡蛋。

//todo

代码

性能

1884.鸡蛋掉落-两枚鸡蛋

目标

给你 2 枚相同 的鸡蛋,和一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都 会碎 ,从 f 楼层或比它低 的楼层落下的鸡蛋都 不会碎 。

每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。

请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

示例 1:

输入:n = 2
输出:2
解释:我们可以将第一枚鸡蛋从 1 楼扔下,然后将第二枚从 2 楼扔下。
如果第一枚鸡蛋碎了,可知 f = 0;
如果第二枚鸡蛋碎了,但第一枚没碎,可知 f = 1;
否则,当两个鸡蛋都没碎时,可知 f = 2。

示例 2:

输入:n = 100
输出:14
解释:
一种最优的策略是:
- 将第一枚鸡蛋从 9 楼扔下。如果碎了,那么 f 在 0 和 8 之间。将第二枚从 1 楼扔下,然后每扔一次上一层楼,在 8 次内找到 f 。总操作次数 = 1 + 8 = 9 。
- 如果第一枚鸡蛋没有碎,那么再把第一枚鸡蛋从 22 层扔下。如果碎了,那么 f 在 9 和 21 之间。将第二枚鸡蛋从 10 楼扔下,然后每扔一次上一层楼,在 12 次内找到 f 。总操作次数 = 2 + 12 = 14 。
- 如果第一枚鸡蛋没有再次碎掉,则按照类似的方法从 34, 45, 55, 64, 72, 79, 85, 90, 94, 97, 99 和 100 楼分别扔下第一枚鸡蛋。
不管结果如何,最多需要扔 14 次来确定 f 。

说明:

  • 1 <= n <= 1000

思路

有一个 1 ~ n 层楼的建筑,存在一个楼层 f,任何大于 f 层落下的鸡蛋都会摔碎。现在有两个鸡蛋,每次操作可以从任意楼层向下扔鸡蛋,如果鸡蛋碎了则无法再使用,求确定 f 值的最小操作次数。

为了确保能够找到 f,如果第一个尝试的鸡蛋碎了,那么另一个鸡蛋只能从已知的安全楼层一层一层向上尝试。

观察示例2,可以从 n 开始 减 1 2 3 …… i 直到小于等于零,返回 i - 1即可。

看了题解,这样做可行的逻辑是这样的:

假设已知最小操作次数 k,我们扔第一枚鸡蛋选第几层?显然,应该选第 k 层,因为如果第一枚鸡蛋碎了,只需要从 1 ~ k - 1 枚举即可。

如果第一枚鸡蛋没碎,那么下一次选第几层?现在还剩下 k - 1 次尝试,所以应该选 k + 1 + (k - 2) = k + (k - 1) 层,因为如果在该层扔鸡蛋碎了,只需从 k + 1 ~ k + k - 2 枚举即可,共 k - 2 次,再加上前面尝试的 2 次,总次数为 k

以此类推,我们可以确定总层数 n = k + (k - 1) + (k - 2) + …… + 2 + 1 = k * (k + 1)/2,解方程得 k = (sqrt(1+8*n) - 1)/2,结果需要向上取整。

代码


/**
 * @date 2024-10-13 19:30
 */
public class TwoEggDrop1884 {

    public int twoEggDrop_v1(int n) {
        return (int) Math.ceil((Math.sqrt(1 + 8 * n) - 1) / 2);
    }

    public int twoEggDrop(int n) {
        int i = 1;
        while (n > 0){
            n -= i++;
        }
        return i - 1;
    }
}

性能

3164.优质数对的总数II

目标

给你两个整数数组 nums1 和 nums2,长度分别为 n 和 m。同时给你一个正整数 k。

如果 nums1[i] 可以被 nums2[j] * k 整除,则称数对 (i, j) 为 优质数对(0 <= i <= n - 1, 0 <= j <= m - 1)。

返回 优质数对 的总数。

示例 1:

输入:nums1 = [1,3,4], nums2 = [1,3,4], k = 1
输出:5
解释:
5个优质数对分别是 (0, 0), (1, 0), (1, 1), (2, 0), 和 (2, 2)。

示例 2:

输入:nums1 = [1,2,4,12], nums2 = [2,4], k = 3
输出:2
解释:
2个优质数对分别是 (3, 0) 和 (3, 1)。

说明:

  • 1 <= n, m <= 10^5
  • 1 <= nums1[i], nums2[j] <= 10^6
  • 1 <= k <= 10^3

思路

这与昨天 3162.优质数对的总数I 的区别是数据范围变大了,暴力解法不可行。

枚举因子的时间复杂度为 O(n * sqrt(max1/k) + m), 代入计算 10^5 * 10^3 + 10^5 大约 10^8,耗时560ms。

枚举倍数的时间复杂度为 O(n + m + max1/k * log(m))10^5 + 10^5 + 10^6 * ln(10^5) ≈ 10^5 + 10^5 + 10^6 * 11.5110^7,耗时 88ms。

代码


/**
 * @date 2024-10-11 8:40
 */
public class NumberOfPairs3164 {

    /**
     * 枚举因子 560ms
     */
    public long numberOfPairs_v1(int[] nums1, int[] nums2, int k) {
        Map<Integer, Integer> factorCnt = new HashMap<>();
        for (int num : nums1) {
            if (num % k != 0) {
                continue;
            }
            num /= k;
            for (int i = 1; i * i <= num; i++) {
                if (num % i != 0) {
                    continue;
                }
                factorCnt.merge(i, 1, Integer::sum);
                if (i * i < num) {
                    factorCnt.merge(num / i, 1, Integer::sum);
                }
            }
        }
        long res = 0L;
        for (int num : nums2) {
            res += factorCnt.getOrDefault(num, 0);
        }
        return res;
    }

    /**
     * 枚举倍数 88ms
     */
    public long numberOfPairs(int[] nums1, int[] nums2, int k) {
        Map<Integer, Integer> map1 = new HashMap<>(nums1.length);
        Map<Integer, Integer> map2 = new HashMap<>(nums2.length);
        int max1 = 0;
        for (int num : nums1) {
            map1.merge(num, 1, Integer::sum);
            max1 = Math.max(max1, num);
        }
        for (int num : nums2) {
            map2.merge(num, 1, Integer::sum);
        }
        long res = 0L;
        for (int num : map2.keySet()) {
            int multiple = num * k;
            for (int i = multiple; i <= max1; i += multiple) {
                if (map1.containsKey(i)) {
                    res += (long) map1.get(i) * map2.get(num);
                }
            }
        }
        return res;
    }

}

性能