2438.二的幂数组中查询范围内的乘积

目标

给你一个正整数 n ,你需要找到一个下标从 0 开始的数组 powers ,它包含 最少 数目的 2 的幂,且它们的和为 n 。powers 数组是 非递减 顺序的。根据前面描述,构造 powers 数组的方法是唯一的。

同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] ,其中 queries[i] 表示请你求出满足 lefti <= j <= righti 的所有 powers[j] 的乘积。

请你返回一个数组 answers ,长度与 queries 的长度相同,其中 answers[i]是第 i 个查询的答案。由于查询的结果可能非常大,请你将每个 answers[i] 都对 10^9 + 7 取余 。

示例 1:

输入:n = 15, queries = [[0,1],[2,2],[0,3]]
输出:[2,4,64]
解释:
对于 n = 15 ,得到 powers = [1,2,4,8] 。没法得到元素数目更少的数组。
第 1 个查询的答案:powers[0] * powers[1] = 1 * 2 = 2 。
第 2 个查询的答案:powers[2] = 4 。
第 3 个查询的答案:powers[0] * powers[1] * powers[2] * powers[3] = 1 * 2 * 4 * 8 = 64 。
每个答案对 10^9 + 7 得到的结果都相同,所以返回 [2,4,64] 。

示例 2:

输入:n = 2, queries = [[0,0]]
输出:[2]
解释:
对于 n = 2, powers = [2] 。
唯一一个查询的答案是 powers[0] = 2 。答案对 10^9 + 7 取余后结果相同,所以返回 [2] 。

说明:

  • 1 <= n <= 10^9
  • 1 <= queries.length <= 10^5
  • 0 <= starti <= endi < powers.length

思路

给定一个正整数,将其拆分成最少数目的 2 的幂,即二进制表示中每一个 1 所表示的 2 的幂,按照从小到大的顺序放入 nums。有一个查询数组 queriesqueries[i] = [from, to] 表示查询 numsfromto 的乘积,返回对应的结果数组。

由于 nums 长度最大 31,因此可以提前预处理所有子数组的乘积,或值直接暴力计算查询范围内的元素乘积。

注意不能计算前缀乘积,为了防止溢出存的是余数,相除后取余并不满足分配律。或者换一种思路计算幂次的前缀和,然后再计算 2 的幂对 mod 取余。

代码


/**
 * @date 2025-08-11 9:52
 */
public class ProductQueries2438 {

    public int[] productQueries(int n, int[][] queries) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 31; i++) {
            int p = 1 << i;
            if ((n & p) == p) {
                list.add(p);
            }
        }
        int mod = 1000000007;
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int from = queries[i][0];
            int to = queries[i][1];
            res[i] = 1;
            for (int j = from; j <= to; j++) {
                res[i] = (int) ((long) res[i] * list.get(j) % mod);
            }
        }
        return res;
    }

}

性能

869.重新排序得到2的幂

目标

给定正整数 n ,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零。

如果我们可以通过上述方式得到 2 的幂,返回 true;否则,返回 false。

示例 1:

输入:n = 1
输出:true

示例 2:

输入:n = 10
输出:false

说明:

  • 1 <= n <= 10^9

思路

判断数字重新排列后能否变成 2 的幂。

统计所有数位上数字的出现次数,如果与 2 的幂完全相同则返回 true。

代码


/**
 * @date 2025-08-10 13:49
 */
public class ReorderedPowerOf2_869 {

    public static int[][] nums = new int[31][10];

    static {
        for (int i = 0; i < 31; i++) {
            int num = 1 << i;
            while (num > 0) {
                int d = num % 10;
                nums[i][d]++;
                num /= 10;
            }
        }
    }

    public boolean reorderedPowerOf2(int n) {
        int[] digits = new int[10];
        while (n > 0) {
            int d = n % 10;
            digits[d]++;
            n /= 10;
        }
        here:
        for (int[] num : nums) {
            for (int i = 0; i < num.length; i++) {
                if (num[i] != digits[i]) {
                    continue here;
                }
            }
            return true;
        }
        return false;
    }

}

性能

231.2的幂

目标

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

示例 1:

输入:n = 1
输出:true
解释:20 = 1

示例 2:

输入:n = 16
输出:true
解释:24 = 16

示例 3:

输入:n = 3
输出:false

提示:

  • -2^31 <= n <= 2^31 - 1

进阶:你能够不使用循环/递归解决此问题吗?

思路

判断数字是不是 2 的幂。

提前预处理 2 的幂。

进阶做法是判断 n & (n - 1) == 0

代码


/**
 * @date 2025-08-09 20:33
 */
public class IsPowerOfTwo231 {

    static Set<Integer> set = new HashSet<>();

    static {
        for (int i = 0; i < 31; i++) {
            set.add(1 << i);
        }
    }

    public boolean isPowerOfTwo(int n) {
        return set.contains(n);
    }

}

性能

808.分汤

目标

你有两种汤,A 和 B,每种初始为 n 毫升。在每一轮中,会随机选择以下四种服务操作中的一种,每种操作的概率为 0.25,且与之前的所有轮次 无关:

  1. 从汤 A 取 100 毫升,从汤 B 取 0 毫升
  2. 从汤 A 取 75 毫升,从汤 B 取 25 毫升
  3. 从汤 A 取 50 毫升,从汤 B 取 50 毫升
  4. 从汤 A 取 25 毫升,从汤 B 取 75 毫升

注意:

  • 不存在先分配 100 ml 汤B 的操作。
  • 汤 A 和 B 在每次操作中同时被倒入。
  • 如果一次操作要求你倒出比剩余的汤更多的量,请倒出该汤剩余的所有部分。

操作过程在任何回合中任一汤被用完后立即停止。

返回汤 A 在 B 前耗尽的概率,加上两种汤在 同一回合 耗尽概率的一半。返回值在正确答案 10^-5 的范围内将被认为是正确的。

示例 1:

输入:n = 50
输出:0.62500
解释:
如果我们选择前两个操作,A 首先将变为空。
对于第三个操作,A 和 B 会同时变为空。
对于第四个操作,B 首先将变为空。
所以 A 变为空的总概率加上 A 和 B 同时变为空的概率的一半是 0.25 *(1 + 1 + 0.5 + 0)= 0.625。

示例 2:

输入:n = 100
输出:0.71875
解释:
如果我们选择第一个操作,A 首先将变为空。
如果我们选择第二个操作,A 将在执行操作 [1, 2, 3] 时变为空,然后 A 和 B 在执行操作 4 时同时变空。
如果我们选择第三个操作,A 将在执行操作 [1, 2] 时变为空,然后 A 和 B 在执行操作 3 时同时变空。
如果我们选择第四个操作,A 将在执行操作 1 时变为空,然后 A 和 B 在执行操作 2 时同时变空。
所以 A 变为空的总概率加上 A 和 B 同时变为空的概率的一半是 0.71875。

说明:

  • 0 <= n <= 10^9

思路

代码

性能

3363.最多可收集的水果数目

目标

有一个游戏,游戏由 n x n 个房间网格状排布组成。

给你一个大小为 n x n 的二维整数数组 fruits ,其中 fruits[i][j] 表示房间 (i, j) 中的水果数目。有三个小朋友 一开始 分别从角落房间 (0, 0) ,(0, n - 1) 和 (n - 1, 0) 出发。

每一位小朋友都会 恰好 移动 n - 1 次,并到达房间 (n - 1, n - 1) :

  • 从 (0, 0) 出发的小朋友每次移动从房间 (i, j) 出发,可以到达 (i + 1, j + 1) ,(i + 1, j) 和 (i, j + 1) 房间之一(如果存在)。
  • 从 (0, n - 1) 出发的小朋友每次移动从房间 (i, j) 出发,可以到达房间 (i + 1, j - 1) ,(i + 1, j) 和 (i + 1, j + 1) 房间之一(如果存在)。
  • 从 (n - 1, 0) 出发的小朋友每次移动从房间 (i, j) 出发,可以到达房间 (i - 1, j + 1) ,(i, j + 1) 和 (i + 1, j + 1) 房间之一(如果存在)。

当一个小朋友到达一个房间时,会把这个房间里所有的水果都收集起来。如果有两个或者更多小朋友进入同一个房间,只有一个小朋友能收集这个房间的水果。当小朋友离开一个房间时,这个房间里不会再有水果。

请你返回三个小朋友总共 最多 可以收集多少个水果。

示例 1:

输入:fruits = [[1,2,3,4],[5,6,8,7],[9,10,11,12],[13,14,15,16]]
输出:100
解释:
这个例子中:
第 1 个小朋友(绿色)的移动路径为 (0,0) -> (1,1) -> (2,2) -> (3, 3) 。
第 2 个小朋友(红色)的移动路径为 (0,3) -> (1,2) -> (2,3) -> (3, 3) 。
第 3 个小朋友(蓝色)的移动路径为 (3,0) -> (3,1) -> (3,2) -> (3, 3) 。
他们总共能收集 1 + 6 + 11 + 1 + 4 + 8 + 12 + 13 + 14 + 15 = 100 个水果。

示例 2:

输入:fruits = [[1,1],[1,1]]
输出:4
解释:
这个例子中:
第 1 个小朋友移动路径为 (0,0) -> (1,1) 。
第 2 个小朋友移动路径为 (0,1) -> (1,1) 。
第 3 个小朋友移动路径为 (1,0) -> (1,1) 。
他们总共能收集 1 + 1 + 1 + 1 = 4 个水果。

说明:

  • 2 <= n == fruits.length == fruits[i].length <= 1000
  • 0 <= fruits[i][j] <= 1000

思路

有一个 n x n 排列的房间,房间中放有水果,fruits[i][j] 表示坐标为 (i, j) 的房间中的水果数量,有三个小朋友 A B C 同时从 (0, 0) (0, n - 1) (n - 1, 0) 出发,A 可以向 走,B 可以向 走,C 可以向 走,每一个小朋友到达一个房间会带走所有水果,求三个小朋友从起点出发,恰好走 n - 1 步 到达 (n - 1, n - 1) 总共可以收集的水果数量。

// 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;
    }

}

性能

904.水果成篮

目标

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

说明:

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

思路

求满足条件的子数组的最大长度,要求子数组最多包含两个不同的元素。

滑动窗口。

代码


/**
 * @date 2025-08-04 8:51
 */
public class TotalFruit904 {

    public int totalFruit(int[] fruits) {
        int n = fruits.length;
        int res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        int l = 0;
        for (int i = 0; i < n; i++) {
            map.merge(fruits[i], 1, Integer::sum);
            while (map.size() > 2) {
                map.merge(fruits[l], -1, Integer::sum);
                if (map.get(fruits[l]) == 0) {
                    map.remove(fruits[l]);
                }
                l++;
            }
            res = Math.max(res, i - l + 1);
        }
        return res;
    }

}

性能

2106.摘水果

目标

在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [positioni, amounti] 表示共有 amounti 个水果放置在 positioni 上。fruits 已经按 positioni 升序排列 ,每个 positioni 互不相同 。

另给你两个整数 startPos 和 k 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 x 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。

返回你可以摘到水果的 最大总数 。

示例 1:

输入:fruits = [[2,8],[6,3],[8,6]], startPos = 5, k = 4
输出:9
解释:
最佳路线为:
- 向右移动到位置 6 ,摘到 3 个水果
- 向右移动到位置 8 ,摘到 6 个水果
移动 3 步,共摘到 3 + 6 = 9 个水果

示例 2:

输入:fruits = [[0,9],[4,1],[5,7],[6,2],[7,4],[10,9]], startPos = 5, k = 4
输出:14
解释:
可以移动最多 k = 4 步,所以无法到达位置 0 和位置 10 。
最佳路线为:
- 在初始位置 5 ,摘到 7 个水果
- 向左移动到位置 4 ,摘到 1 个水果
- 向右移动到位置 6 ,摘到 2 个水果
- 向右移动到位置 7 ,摘到 4 个水果
移动 1 + 3 = 4 步,共摘到 7 + 1 + 2 + 4 = 14 个水果

示例 3:

输入:fruits = [[0,3],[6,4],[8,5]], startPos = 3, k = 2
输出:0
解释:
最多可以移动 k = 2 步,无法到达任一有水果的地方

说明:

  • 1 <= fruits.length <= 10^5
  • fruits[i].length == 2
  • 0 <= startPos, positioni <= 2 * 10^5
  • 对于任意 i > 0 ,positioni-1 < positioni 均成立(下标从 0 开始计数)
  • 1 <= amounti <= 10^4
  • 0 <= k <= 2 * 10^5

思路

代码

性能

2561.重排水果

目标

你有两个果篮,每个果篮中有 n 个水果。给你两个下标从 0 开始的整数数组 basket1 和 basket2 ,用以表示两个果篮中每个水果的交换成本。你想要让两个果篮相等。为此,可以根据需要多次执行下述操作:

  • 选中两个下标 i 和 j ,并交换 basket1 中的第 i 个水果和 basket2 中的第 j 个水果。
  • 交换的成本是 min(basket1i,basket2j) 。

根据果篮中水果的成本进行排序,如果排序后结果完全相同,则认为两个果篮相等。

返回使两个果篮相等的最小交换成本,如果无法使两个果篮相等,则返回 -1 。

示例 1:

输入:basket1 = [4,2,2,2], basket2 = [1,4,1,2]
输出:1
解释:交换 basket1 中下标为 1 的水果和 basket2 中下标为 0 的水果,交换的成本为 1 。此时,basket1 = [4,1,2,2] 且 basket2 = [2,4,1,2] 。重排两个数组,发现二者相等。

示例 2:

输入:basket1 = [2,3,4,1], basket2 = [3,2,5,1]
输出:-1
解释:可以证明无法使两个果篮相等。

说明:

  • basket1.length == bakste2.length
  • 1 <= basket1.length <= 10^5
  • 1 <= basket1i,basket2i <= 10^9

思路

代码

性能