2048.下一个更大的数值平衡数

目标

如果整数 x 满足:对于每个数位 d ,这个数位 恰好 在 x 中出现 d 次。那么整数 x 就是一个 数值平衡数 。

给你一个整数 n ,请你返回 严格大于 n 的 最小数值平衡数 。

示例 1:

输入:n = 1
输出:22
解释:
22 是一个数值平衡数,因为:
- 数字 2 出现 2 次 
这也是严格大于 1 的最小数值平衡数。

示例 2:

输入:n = 1000
输出:1333
解释:
1333 是一个数值平衡数,因为:
- 数字 1 出现 1 次。
- 数字 3 出现 3 次。 
这也是严格大于 1000 的最小数值平衡数。
注意,1022 不能作为本输入的答案,因为数字 0 的出现次数超过了 0 。

示例 3:

输入:n = 3000
输出:3133
解释:
3133 是一个数值平衡数,因为:
- 数字 1 出现 1 次。
- 数字 3 出现 3 次。 
这也是严格大于 3000 的最小数值平衡数。

说明:

  • 0 <= n <= 10^6

思路

定义平衡数中每一位上数字的出现次数 cnt[d] = d,判断大于 n 的最小平衡数。

回溯构造 10^7 范围内的所有平衡数,然后二分查找。

官网题解是枚举所有大于 n 的整数,判断是否是平衡数,也可以预处理 (n, 1224444] 范围内的所有平衡数,然后二分查找。

代码


/**
 * @date 2025-10-24 9:12
 */
public class NextBeautifulNumber2048 {

    public int nextBeautifulNumber(int n) {
        return set.higher(n);
    }

    private static final TreeSet<Integer> set = new TreeSet<>();

    static {
        int[] cnt = new int[7];
        Arrays.setAll(cnt, i -> i);
        dfs(0, 0, 7, cnt);
    }

    public static void dfs(int cur, int l, int length, int[] cnt) {
        if (l > length + 1) {
            return;
        }
        boolean balance = true;
        for (int i = 1; i < cnt.length; i++) {
            if (cnt[i] != i && cnt[i] != 0) {
                balance = false;
                break;
            }
        }
        if (balance) {
            set.add(cur);
        }
        for (int i = 1; i < cnt.length; i++) {
            if (cnt[i] == 0) {
                continue;
            }
            cnt[i]--;
            dfs(cur * 10 + i, l + 1, length, cnt);
            cnt[i]++;
        }
    }

}

性能

3346.执行操作后元素的最高频率I

目标

给你一个整数数组 nums 和两个整数 k 和 numOperations 。

你必须对 nums 执行 操作 numOperations 次。每次操作中,你可以:

  • 选择一个下标 i ,它在之前的操作中 没有 被选择过。
  • 将 nums[i] 增加范围 [-k, k] 中的一个整数。

在执行完所有操作以后,请你返回 nums 中出现 频率最高 元素的出现次数。

一个元素 x 的 频率 指的是它在数组中出现的次数。

示例 1:

输入:nums = [1,4,5], k = 1, numOperations = 2
输出:2
解释:
通过以下操作得到最高频率 2 :
将 nums[1] 增加 0 ,nums 变为 [1, 4, 5] 。
将 nums[2] 增加 -1 ,nums 变为 [1, 4, 4] 。

示例 2:

输入:nums = [5,11,20,20], k = 5, numOperations = 1
输出:2
解释:
通过以下操作得到最高频率 2 :
将 nums[1] 增加 0 。

说明:

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

思路

对数组 nums 执行 numOperations 次操作,每次操作可以选一个没有被操作过的元素,将其增加 [-k, k] 之间的整数,求元素值的最大出现次数。

对数组排序,并针对元素进行计数,枚举值域范围内的每一个值,二分查找左右 k 范围内的元素个数。

代码


/**
 * @date 2025-10-21 8:53
 */
public class MaxFrequency3346 {

    public int maxFrequency(int[] nums, int k, int numOperations) {
        int max = Arrays.stream(nums).max().getAsInt();
        int[] cnt = new int[max + 1];
        for (int num : nums) {
            cnt[num]++;
        }
        Arrays.sort(nums);
        int res = 1;
        for (int i = 1; i <= max; i++) {
            int l = lowerBound(nums, i - k);
            int r = upperBound(nums, i + k);
            res = Math.max(res, cnt[i] + Math.min(r - l + 1 - cnt[i], numOperations));
        }
        return res;
    }

    public int lowerBound(int[] nums, int target) {
        int r = nums.length - 1;
        int l = 0;
        int mid = l + (r - l) / 2;
        while (l <= r) {
            if (nums[mid] >= target) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
            mid = l + (r - l) / 2;
        }
        return l;
    }

    public int upperBound(int[] nums, int target) {
        int r = nums.length - 1;
        int l = 0;
        int mid = l + (r - l) / 2;
        while (l <= r) {
            if (nums[mid] <= target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
            mid = l + (r - l) / 2;
        }
        return r;
    }

}

性能

3494.酿造药水需要的最少总时间

目标

给你两个长度分别为 n 和 m 的整数数组 skill 和 mana 。

在一个实验室里,有 n 个巫师,他们必须按顺序酿造 m 个药水。每个药水的法力值为 mana[j],并且每个药水 必须 依次通过 所有 巫师处理,才能完成酿造。第 i 个巫师在第 j 个药水上处理需要的时间为 timeij = skill[i] * mana[j]。

由于酿造过程非常精细,药水在当前巫师完成工作后 必须 立即传递给下一个巫师并开始处理。这意味着时间必须保持 同步,确保每个巫师在药水到达时 马上 开始工作。

返回酿造所有药水所需的 最短 总时间。

示例 1:

输入: skill = [1,5,2,4], mana = [5,1,4,2]
输出: 110
解释:
药水编号 开始时间 巫师 0 完成时间 巫师 1 完成时间 巫师 2 完成时间 巫师 3 完成时间
0  0  5 30 40 60
1 52 53 58 60 64
2 54 58 78 86 102
3 86 88 98 102 110
举个例子,为什么巫师 0 不能在时间 t = 52 前开始处理第 1 个药水,假设巫师们在时间 t = 50 开始准备第 1 个药水。时间 t = 58 时,巫师 2 已经完成了第 1 个药水的处理,但巫师 3 直到时间 t = 60 仍在处理第 0 个药水,无法马上开始处理第 1个药水。

示例 2:

输入: skill = [1,1,1], mana = [1,1,1]
输出: 5
解释:
第 0 个药水的准备从时间 t = 0 开始,并在时间 t = 3 完成。
第 1 个药水的准备从时间 t = 1 开始,并在时间 t = 4 完成。
第 2 个药水的准备从时间 t = 2 开始,并在时间 t = 5 完成。

示例 3:

输入: skill = [1,2,3,4], mana = [1,2]
输出: 21

说明:

  • n == skill.length
  • m == mana.length
  • 1 <= n, m <= 5000
  • 1 <= mana[i], skill[i] <= 5000

思路

有 n 个巫师按顺序酿造 m 个药水,每个药水必须按顺序依次由 n 个巫师处理。第 i 个巫师处理第 j 个药水的时间为 skill[i] * mana[j]。求酿造所有药水所需的最短总时间。

定义 dp[i][j] 表示巫师 j - 1 制作完第 i - 1 瓶药的最短时间,状态转移方程为 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + skill[j - 1] * mana[i - 1],即前面巫师的完成时间以及当前巫师上一轮的完成时间。当前药水完成之后需要倒序更新各个巫师的完成时间,因为状态转移方程只能保证最后一个巫师完成的时间是正确的,前面巫师的完成时间应该由最后一个完成时间来倒推。如果不更新会导致下一瓶药水的制作时间偏早。

// todo 学习其它题解

代码


/**
 * @date 2025-10-09 9:02
 */
public class MinTime3494 {

    public long minTime(int[] skill, int[] mana) {
        int n = skill.length;
        int m = mana.length;
        long[][] dp = new long[m + 1][n + 2];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + skill[j - 1] * mana[i - 1];
            }
            for (int j = n - 1; j >= 1; j--) {
                dp[i][j] = dp[i][j + 1] - skill[j] * mana[i - 1];
            }
        }
        return dp[m][n];
    }

}

性能

2300.咒语和药水的成功对数

目标

给你两个正整数数组 spells 和 potions ,长度分别为 n 和 m ,其中 spells[i] 表示第 i 个咒语的能量强度,potions[j] 表示第 j 瓶药水的能量强度。

同时给你一个整数 success 。一个咒语和药水的能量强度 相乘 如果 大于等于 success ,那么它们视为一对 成功 的组合。

请你返回一个长度为 n 的整数数组 pairs,其中 pairs[i] 是能跟第 i 个咒语成功组合的 药水 数目。

示例 1:

输入:spells = [5,1,3], potions = [1,2,3,4,5], success = 7
输出:[4,0,3]
解释:
- 第 0 个咒语:5 * [1,2,3,4,5] = [5,10,15,20,25] 。总共 4 个成功组合。
- 第 1 个咒语:1 * [1,2,3,4,5] = [1,2,3,4,5] 。总共 0 个成功组合。
- 第 2 个咒语:3 * [1,2,3,4,5] = [3,6,9,12,15] 。总共 3 个成功组合。
所以返回 [4,0,3] 。

示例 2:

输入:spells = [3,1,2], potions = [8,5,8], success = 16
输出:[2,0,2]
解释:
- 第 0 个咒语:3 * [8,5,8] = [24,15,24] 。总共 2 个成功组合。
- 第 1 个咒语:1 * [8,5,8] = [8,5,8] 。总共 0 个成功组合。
- 第 2 个咒语:2 * [8,5,8] = [16,10,16] 。总共 2 个成功组合。
所以返回 [2,0,2] 。

说明:

  • n == spells.length
  • m == potions.length
  • 1 <= n, m <= 10^5
  • 1 <= spells[i], potions[i] <= 10^5
  • 1 <= success <= 10^10

思路

n 个咒语 和 m 瓶药水,对于每一个咒语,如果它的强度 spells[i] 与 药水能量强度 potions[j] 的乘积 大于等于 success 称为一个成功的组合,返回每个咒语的成功组合数。

将药水按强度排序,二分查找最后一个不成功组合的下标,成功的组合数为 m - (index + 1)

代码


/**
 * @date 2025-10-09 11:32
 */
public class SuccessfulPairs2300 {

    public int[] successfulPairs(int[] spells, int[] potions, long success) {
        Arrays.sort(potions);
        int n = potions.length;
        for (int i = 0; i < spells.length; i++) {
            int index = bs(potions, spells[i], success);
            spells[i] = n - 1 - index;
        }
        return spells;
    }

    public int bs(int[] potions, long spell, long success) {
        int r = potions.length - 1;
        int l = 0;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (spell * potions[m] >= success) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }

}

性能

611.有效三角形的个数

目标

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

示例 1:

输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是: 
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

示例 2:

输入: nums = [4,2,3,4]
输出: 4

说明:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

思路

有一个非负整数的数组,返回其中可以组成三角形的三元组。

可以先排序,然后使用二重循环,二分查找满足条件的第三边。

代码


/**
 * @date 2025-09-26 8:52
 */
public class TriangleNumber611 {

    public int triangleNumber(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int t = nums[i] + nums[j];
                int index = bs(nums, j, t);
                res += Math.max(0, index - j);
            }
        }
        return res;
    }

    public int bs(int[] nums, int l, int target) {
        int r = nums.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (nums[m] >= target) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }

}

性能

3508.设计路由器

目标

请你设计一个数据结构来高效管理网络路由器中的数据包。每个数据包包含以下属性:

  • source:生成该数据包的机器的唯一标识符。
  • destination:目标机器的唯一标识符。
  • timestamp:该数据包到达路由器的时间戳。

实现 Router 类:

Router(int memoryLimit):初始化路由器对象,并设置固定的内存限制。

  • memoryLimit 是路由器在任意时间点可以存储的 最大 数据包数量。
  • 如果添加一个新数据包会超过这个限制,则必须移除 最旧的 数据包以腾出空间。

bool addPacket(int source, int destination, int timestamp):将具有给定属性的数据包添加到路由器。

  • 如果路由器中已经存在一个具有相同 source、destination 和 timestamp 的数据包,则视为重复数据包。
  • 如果数据包成功添加(即不是重复数据包),返回 true;否则返回 false。

int[] forwardPacket():以 FIFO(先进先出)顺序转发下一个数据包。

  • 从存储中移除该数据包。
  • 以数组 [source, destination, timestamp] 的形式返回该数据包。
  • 如果没有数据包可以转发,则返回空数组。

int getCount(int destination, int startTime, int endTime):

  • 返回当前存储在路由器中(即尚未转发)的,且目标地址为指定 destination 且时间戳在范围 [startTime, endTime](包括两端)内的数据包数量。

注意:对于 addPacket 的查询会按照 timestamp 的递增顺序进行。

示例 1:

输入:
["Router", "addPacket", "addPacket", "addPacket", "addPacket", "addPacket", "forwardPacket", "addPacket", "getCount"]
[[3], [1, 4, 90], [2, 5, 90], [1, 4, 90], [3, 5, 95], [4, 5, 105], [], [5, 2, 110], [5, 100, 110]]
输出:
[null, true, true, false, true, true, [2, 5, 90], true, 1]
解释:
Router router = new Router(3); // 初始化路由器,内存限制为 3。
router.addPacket(1, 4, 90); // 数据包被添加,返回 True。
router.addPacket(2, 5, 90); // 数据包被添加,返回 True。
router.addPacket(1, 4, 90); // 这是一个重复数据包,返回 False。
router.addPacket(3, 5, 95); // 数据包被添加,返回 True。
router.addPacket(4, 5, 105); // 数据包被添加,[1, 4, 90] 被移除,因为数据包数量超过限制,返回 True。
router.forwardPacket(); // 转发数据包 [2, 5, 90] 并将其从路由器中移除。
router.addPacket(5, 2, 110); // 数据包被添加,返回 True。
router.getCount(5, 100, 110); // 唯一目标地址为 5 且时间在 [100, 110] 范围内的数据包是 [4, 5, 105],返回 1。

示例 2:

输入:
["Router", "addPacket", "forwardPacket", "forwardPacket"]
[[2], [7, 4, 90], [], []]
输出:
[null, true, [7, 4, 90], []]
解释:
Router router = new Router(2); // 初始化路由器,内存限制为 2。
router.addPacket(7, 4, 90); // 返回 True。
router.forwardPacket(); // 返回 [7, 4, 90]。
router.forwardPacket(); // 没有数据包可以转发,返回 []。

说明:

  • 2 <= memoryLimit <= 10^5
  • 1 <= source, destination <= 2 * 10^5
  • 1 <= timestamp <= 10^9
  • 1 <= startTime <= endTime <= 10^9
  • addPacket、forwardPacket 和 getCount 方法的总调用次数最多为 10^5。
  • 对于 addPacket 的查询,timestamp 按递增顺序给出。

思路

//todo

代码

性能

3479.水果成篮III

目标

给你两个长度为 n 的整数数组,fruits 和 baskets,其中 fruits[i] 表示第 i 种水果的 数量,baskets[j] 表示第 j 个篮子的 容量。

你需要对 fruits 数组从左到右按照以下规则放置水果:

  • 每种水果必须放入第一个 容量大于等于 该水果数量的 最左侧可用篮子 中。
  • 每个篮子只能装 一种 水果。
  • 如果一种水果 无法放入 任何篮子,它将保持 未放置。

返回所有可能分配完成后,剩余未放置的水果种类的数量。

示例 1

输入: fruits = [4,2,5], baskets = [3,5,4]
输出: 1
解释:
fruits[0] = 4 放入 baskets[1] = 5。
fruits[1] = 2 放入 baskets[0] = 3。
fruits[2] = 5 无法放入 baskets[2] = 4。
由于有一种水果未放置,我们返回 1。

示例 2

输入: fruits = [3,6,1], baskets = [6,4,7]
输出: 0
解释:
fruits[0] = 3 放入 baskets[0] = 6。
fruits[1] = 6 无法放入 baskets[1] = 4(容量不足),但可以放入下一个可用的篮子 baskets[2] = 7。
fruits[2] = 1 放入 baskets[1] = 4。
由于所有水果都已成功放置,我们返回 0。

说明:

  • n == fruits.length == baskets.length
  • 1 <= n <= 10^5
  • 1 <= fruits[i], baskets[i] <= 10^9

思路

有水果 fruits 与 篮子 baskets 两个数组,fruits[i] 表示下标为 i 的水果数量,baskets[i] 表示下标为 i 位置上篮子的容量。现在需要按 fruits 的顺序从左往右将其放入篮子中,放置的位置是第一个能够容下水果的篮子,每个篮子只能放一种水果,如果没有位置能放下则保持未放置状态,求剩余未放置的水果种类。

暴力解法是枚举每种水果数量,然后枚举篮子,标记第一个能放下的篮子,时间复杂度为 O(n^2)。由于 basket 是无序的,没办法使用二分查找,并且排序会影响最终结果。因为未排序前,数量小的水果可能占据了大的篮子,导致数量多的水果无法放置。

考虑将 basket 分块,记录每一块的最大值,然后找到块内第一个大于 fruits[i] 的元素,然后将其置零。

代码


/**
 * @date 2025-08-05 15:20
 */
public class NumOfUnplacedFruits3479 {

    public int numOfUnplacedFruits(int[] fruits, int[] baskets) {
        int n = fruits.length;
        int blockSize = (int) Math.ceil(Math.sqrt(n));
        int m = (int) Math.ceil((double) n / blockSize);
        int[] block = new int[m];
        int bi = 0;
        while (bi < m) {
            int start = bi * blockSize;
            int max = 0;
            for (int j = start; j < start + blockSize && j < n; j++) {
                max = Math.max(max, baskets[j]);
            }
            block[bi++] = max;
        }
        int res = n;
        for (int fruit : fruits) {
            int i = 0;
            for (; i < m; i++) {
                if (fruit <= block[i]) {
                    res--;
                    break;
                }
            }
            int start = i * blockSize;
            int newMax = 0;
            for (int j = start; j < start + blockSize && j < n; j++) {
                if (baskets[j] >= fruit) {
                    if (baskets[j] == block[i]) {
                        for (int k = j + 1; k < start + blockSize && k < n; k++) {
                            newMax = Math.max(newMax, baskets[k]);
                        }
                        block[i] = newMax;
                    }
                    baskets[j] = 0;
                    break;
                } else {
                    newMax = Math.max(newMax, baskets[j]);
                }
            }
        }
        return res;
    }

}

性能

3477.水果成篮II

目标

给你两个长度为 n 的整数数组,fruits 和 baskets,其中 fruits[i] 表示第 i 种水果的 数量,baskets[j] 表示第 j 个篮子的 容量。

你需要对 fruits 数组从左到右按照以下规则放置水果:

  • 每种水果必须放入第一个 容量大于等于 该水果数量的 最左侧可用篮子 中。
  • 每个篮子只能装 一种 水果。
  • 如果一种水果 无法放入 任何篮子,它将保持 未放置。

返回所有可能分配完成后,剩余未放置的水果种类的数量。

示例 1

输入: fruits = [4,2,5], baskets = [3,5,4]
输出: 1
解释:
fruits[0] = 4 放入 baskets[1] = 5。
fruits[1] = 2 放入 baskets[0] = 3。
fruits[2] = 5 无法放入 baskets[2] = 4。
由于有一种水果未放置,我们返回 1。

示例 2

输入: fruits = [3,6,1], baskets = [6,4,7]
输出: 0
解释:
fruits[0] = 3 放入 baskets[0] = 6。
fruits[1] = 6 无法放入 baskets[1] = 4(容量不足),但可以放入下一个可用的篮子 baskets[2] = 7。
fruits[2] = 1 放入 baskets[1] = 4。
由于所有水果都已成功放置,我们返回 0。

说明:

  • n == fruits.length == baskets.length
  • 1 <= n <= 100
  • 1 <= fruits[i], baskets[i] <= 1000

思路

有一个数组 basketsfruits,从左到右遍历 fruits,并删除 baskets 中第一个大于等于 fruits[i] 的元素,返回 baskets 最后剩余元素个数。

依题意模拟。

代码


/**
 * @date 2025-08-05 9:10
 */
public class NumOfUnplacedFruits3477 {

    public int numOfUnplacedFruits(int[] fruits, int[] baskets) {
        int n = fruits.length;
        int res = n;
        for (int fruit : fruits) {
            for (int i = 0; i < n; i++) {
                if (baskets[i] >= fruit){
                    res--;
                    baskets[i] = 0;
                    break;
                }
            }
        }
        return res;
    }

}

性能

1751.最多可以参加的会议数目II

目标

给你一个 events 数组,其中 events[i] = [startDayi, endDayi, valuei] ,表示第 i 个会议在 startDayi 天开始,第 endDayi 天结束,如果你参加这个会议,你能得到价值 valuei 。同时给你一个整数 k 表示你能参加的最多会议数目。

你同一时间只能参加一个会议。如果你选择参加某个会议,那么你必须 完整 地参加完这个会议。会议结束日期是包含在会议内的,也就是说你不能同时参加一个开始日期与另一个结束日期相同的两个会议。

请你返回能得到的会议价值 最大和 。

示例 1:

输入:events = [[1,2,4],[3,4,3],[2,3,1]], k = 2
输出:7
解释:选择绿色的活动会议 0 和 1,得到总价值和为 4 + 3 = 7 。

示例 2:

输入:events = [[1,2,4],[3,4,3],[2,3,10]], k = 2
输出:10
解释:参加会议 2 ,得到价值和为 10 。
你没法再参加别的会议了,因为跟会议 2 有重叠。你 不 需要参加满 k 个会议。

示例 3:

输入:events = [[1,1,1],[2,2,2],[3,3,3],[4,4,4]], k = 3
输出:9
解释:尽管会议互不重叠,你只能参加 3 个会议,所以选择价值最大的 3 个会议。

说明:

  • 1 <= k <= events.length
  • 1 <= k * events.length <= 10^6
  • 1 <= startDayi <= endDayi <= 10^9
  • 1 <= valuei <= 10^6

思路

n 个会议中选择 k 个日期不冲突的参加,求参加会议的最大价值之和。

1353.最多可以参加的会议数目 不同之处在于必须从头到尾全程参会,并且最多参加 k 个。

定义 dp[i][k] 表示前 i - 1 个会议至多参加 k 个所能获得的最大价值。i == 0 表示没有参加任何会议。

按照结束时间排序,如果选择当前会议,则需要找到最后一个结束时间小于当前开始时间的下标 j,可以使用二分查找。

代码


/**
 * @date 2025-07-08 8:47
 */
public class MaxValue1751 {

    public int maxValue(int[][] events, int k) {
        Arrays.sort(events, (a, b) -> a[1] - b[1]);
        int n = events.length;
        int[][] dp = new int[n + 1][k + 1];
        for (int i = 0; i < n; i++) {
            int prev = find(events, events[i][0]);
            for (int j = 1; j <= k; j++) {
                dp[i + 1][j] = Math.max(dp[i][j], dp[prev + 1][j - 1] + events[i][2]);
            }
        }
        return dp[n][k];
    }

    public int find(int[][] events, int target) {
        int r = events.length - 1;
        int l = 0;
        int mid = l + (r - l) / 2;
        while (l <= r) {
            if (events[mid][1] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
            mid = l + (r - l) / 2;
        }
        return r;
    }

}

性能

2040.两个有序数组的第K小乘积

目标

给你两个 从小到大排好序 且下标从 0 开始的整数数组 nums1 和 nums2 以及一个整数 k ,请你返回第 k (从 1 开始编号)小的 nums1[i] * nums2[j] 的乘积,其中 0 <= i < nums1.length 且 0 <= j < nums2.length 。

示例 1:

输入:nums1 = [2,5], nums2 = [3,4], k = 2
输出:8
解释:第 2 小的乘积计算如下:
- nums1[0] * nums2[0] = 2 * 3 = 6
- nums1[0] * nums2[1] = 2 * 4 = 8
第 2 小的乘积为 8 。

示例 2:

输入:nums1 = [-4,-2,0,3], nums2 = [2,4], k = 6
输出:0
解释:第 6 小的乘积计算如下:
- nums1[0] * nums2[1] = (-4) * 4 = -16
- nums1[0] * nums2[0] = (-4) * 2 = -8
- nums1[1] * nums2[1] = (-2) * 4 = -8
- nums1[1] * nums2[0] = (-2) * 2 = -4
- nums1[2] * nums2[0] = 0 * 2 = 0
- nums1[2] * nums2[1] = 0 * 4 = 0
第 6 小的乘积为 0 。

示例 3:

输入:nums1 = [-2,-1,0,1,2], nums2 = [-3,-1,2,4,5], k = 3
输出:-6
解释:第 3 小的乘积计算如下:
- nums1[0] * nums2[4] = (-2) * 5 = -10
- nums1[0] * nums2[3] = (-2) * 4 = -8
- nums1[4] * nums2[0] = 2 * (-3) = -6
第 3 小的乘积为 -6 。

说明:

  • 1 <= nums1.length, nums2.length <= 5 * 10^4
  • -10^5 <= nums1[i], nums2[j] <= 10^5
  • 1 <= k <= nums1.length * nums2.length
  • nums1 和 nums2 都是从小到大排好序的。

思路

有两个有序数组 nums1nums2,从两个数组中中各取一个数相乘,返回第 k 小的乘积。

代码

性能