LeetCode2921~2940题目解析

LeetCode 2921-2940. 题解合集

文章目录


2921. Maximum Profitable Triplets With Increasing Prices II(价格递增的最大利润三元组 II)

题目描述

给定长度为 n 的数组 prices 和 profits。商店有 n 个商品,第 i 个商品价格为 prices[i]、

利润为 profits[i]。选择三个商品满足 prices[i] < prices[j] < prices[k](i<j<k),

利润 = profits[i] + profits[j] + profits[k]。返回最大利润;否则 -1。

示例 1

复制代码
prices = [10,2,3,4], profits = [100,2,7,10] -> 19

示例 2

复制代码
prices = [1,2,3,4,5], profits = [1,5,3,4,6] -> 15

示例 3

复制代码
prices = [4,3,2,1], profits = [33,20,19,87] -> -1

提示:

  • 3 <= prices.length == profits.length <= 50000
  • 1 <= prices[i] <= 5000
  • 1 <= profits[i] <= 10^6

解题思路

解题思路:值域树状数组

价格值域 <= 5000,可用按"价格"索引的 BIT 维护最大利润:

  • 左侧:从左往右扫,正向索引(价格 p -> p),query(pricesj-1) 得价格 < pricesj
    的最大利润 leftBestj;
  • 右侧:从右往左扫,倒置索引(价格 p -> maxP-p+1,价格越大索引越小),
    价格 > pricesj 的最大利润 = query(maxP - pricesj),存 rightBestj。
    最后遍历 j 汇总 leftBestj + profitsj + rightBestj。
    O(n log maxP),满足 5e4 数据量。
    复杂度:
  • 时间复杂度:O(n log maxP)
  • 空间复杂度:O(maxP + n)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    private static final int MAXP = 5000;

    public int maxProfit(int[] prices, int[] profits) {
        int n = prices.length;
        // 右侧:倒置索引 BIT,预计算 rightBest[j]
        int[] rightBest = new int[n];
        Arrays.fill(rightBest, -1);
        BIT right = new BIT(MAXP);
        for (int j = n - 1; j >= 0; j--) {
            int p = prices[j];
            // 价格 > p 的最大利润 = 倒置索引 query(MAXP - p)
            int q = right.query(MAXP - p);
            rightBest[j] = q == 0 ? -1 : q;
            right.update(MAXP - p + 1, profits[j]);
        }
        // 左侧:正向索引 BIT,边扫边查
        BIT left = new BIT(MAXP);
        int ans = -1;
        for (int j = 0; j < n; j++) {
            int p = prices[j];
            int lq = left.query(p - 1); // 价格 < p
            if (lq > 0 && rightBest[j] != -1) {
                ans = Math.max(ans, lq + profits[j] + rightBest[j]);
            }
            left.update(p, profits[j]);
        }
        return ans;
    }

    // 树状数组:单点更新(取 max),前缀查询(取 max)
    static class BIT {
        int n;
        int[] tree;

        BIT(int n) {
            this.n = n;
            this.tree = new int[n + 2];
        }

        void update(int i, int v) {
            for (; i <= n; i += i & (-i)) {
                tree[i] = Math.max(tree[i], v);
            }
        }

        int query(int i) {
            if (i <= 0) {
                return 0;
            }
            int r = 0;
            for (; i > 0; i -= i & (-i)) {
                r = Math.max(r, tree[i]);
            }
            return r;
        }
    }

    // ========== 本地 main 自测 ==========

    // 暴力对照
    private static int brute(int[] prices, int[] profits) {
        int n = prices.length;
        int best = -1;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    if (prices[i] < prices[j] && prices[j] < prices[k]) {
                        best = Math.max(best, profits[i] + profits[j] + profits[k]);
                    }
                }
            }
        }
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{10, 2, 3, 4}, new int[]{100, 2, 7, 10}, 19);
        check(s, new int[]{1, 2, 3, 4, 5}, new int[]{1, 5, 3, 4, 6}, 15);
        check(s, new int[]{4, 3, 2, 1}, new int[]{33, 20, 19, 87}, -1);

        // 随机对拍
        Random rnd = new Random(37);
        for (int t = 0; t < 300; t++) {
            int n = 3 + rnd.nextInt(9);
            int[] prices = new int[n];
            int[] profits = new int[n];
            for (int i = 0; i < n; i++) {
                prices[i] = 1 + rnd.nextInt(20);
                profits[i] = 1 + rnd.nextInt(20);
            }
            int exp = brute(prices, profits);
            int got = s.maxProfit(prices, profits);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " prices=" + Arrays.toString(prices)
                        + " profits=" + Arrays.toString(profits) + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] prices, int[] profits, int exp) {
        int got = s.maxProfit(prices, profits);
        int exp2 = brute(prices, profits);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " prices="
                + Arrays.toString(prices) + " profits=" + Arrays.toString(profits)
                + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n log maxP)
  • 空间复杂度:O(maxP + n)

2922. Market Analysis III(市场分析 III)

题目类型:数据库(SQL)

题目描述

表: Users

复制代码
+----------------+---------+
| Column Name    | Type    |
+----------------+---------+
| seller_id      | int     |
| join_date      | date    |
| favorite_brand | varchar |
+----------------+---------+
seller_id 是该表具有唯一值的列。
该表包含卖家的 ID, 加入日期以及最喜欢的品牌。

表: Items

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| item_id       | int     |
| item_brand    | varchar |
+---------------+---------+
item_id 是该表具有唯一值的列。
该表包含商品 ID 和商品品牌。

表: Orders

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| order_id      | int     |
| order_date    | date    |
| item_id       | int     |
| seller_id     | int     |
+---------------+---------+
order_id 是该表具有唯一值的列。
item_id 是指向 Items 表的外键。
seller_id 是指向 Users 表的外键。
该表包含订单 ID、下单日期、商品 ID 和卖家 ID。

编写一个解决方案,找到卖出数量 **最多 **的 非 最喜欢的品牌的 不同 商品的顶级卖家。如果有多个卖家有相同的最高卖出数量,返回所有这些卖家。

返回按 seller_id 升序排序 的结果表。

结果格式如下示例所示。

示例 1:

复制代码
输入:
Users table:
+-----------+------------+----------------+
| seller_id | join_date  | favorite_brand |
+-----------+------------+----------------+
| 1         | 2019-01-01 | Lenovo         |
| 2         | 2019-02-09 | Samsung        |
| 3         | 2019-01-19 | LG             |
+-----------+------------+----------------+
Orders table:
+----------+------------+---------+-----------+
| order_id | order_date | item_id | seller_id |
+----------+------------+---------+-----------+
| 1        | 2019-08-01 | 4       | 2         |
| 2        | 2019-08-02 | 2       | 3         |
| 3        | 2019-08-03 | 3       | 3         |
| 4        | 2019-08-04 | 1       | 2         |
| 5        | 2019-08-04 | 4       | 2         |
+----------+------------+---------+-----------+
Items table:
+---------+------------+
| item_id | item_brand |
+---------+------------+
| 1       | Samsung    |
| 2       | Lenovo     |
| 3       | LG         |
| 4       | HP         |
+---------+------------+
输出:
+-----------+-----------+
| seller_id | num_items |
+-----------+-----------+
| 2         | 1         |
| 3         | 1         |
+-----------+-----------+
解释:
- 卖家 ID 为 2 的用户销售了三件商品,但只有两件不是他最喜欢的商品。由于这两个商品相同,所以我们只计数 1。
- 卖家 ID 为 3 的用户销售了两件商品,但只有一件不是他最喜欢的商品。我们将只把非最喜欢商品列入计数中。
因为卖家 ID 为 2 和 3 的卖家都有一件商品列入计数,所以他们都将显示在输出中。

解题思路

解法一:多表连接 + 计数 + 最大值筛选(主提交版本)

  • Orders JOIN Users USING (seller_id) JOIN Items USING (item_id) 三表关联;
  • 过滤 item_brand != favorite_brand,按 seller_id 分组统计
    COUNT(DISTINCT item_id) 得售出的非偏好品牌商品数;
  • 用子查询 MAX(num_items) 取最大数,筛出并列的所有卖家,按 seller_id 升序。

代码实现

sql 复制代码
WITH
    T AS (
        SELECT seller_id, COUNT(DISTINCT item_id) AS num_items
        FROM
            Orders
            JOIN Users USING (seller_id)
            JOIN Items USING (item_id)
        WHERE item_brand != favorite_brand
        GROUP BY 1
    )
SELECT seller_id, num_items
FROM T
WHERE num_items = (SELECT MAX(num_items) FROM T)
ORDER BY 1;

复杂度分析

  • 时间复杂度 :O(n),n 为表行数,单次扫描聚合(或窗口/连接)。
  • 空间复杂度 :O(n),分组/排序/窗口需要存储结果集。

2923. Find Champion I(找到冠军 I)

题目描述

有 n 支队伍(0...n-1)。grid 是 n*n 布尔矩阵:grid[i][j]==1 表示 i 队比 j 队强,

否则 j 队比 i 队强。若不存在强于 a 队的队伍,则 a 队是冠军。返回冠军队伍。

题目保证强弱关系满足传递性。

示例 1

复制代码
grid = [[0,1],[0,0]] -> 0

示例 2

复制代码
grid = [[0,0,1],[1,0,1],[0,0,0]] -> 1

提示:

  • 2 <= n <= 100
  • grid[i][j] != grid[j][i](i != j),grid[i][i] = 0

解题思路

解题思路:入度统计

冠军 = 没有任何队伍强于它,即列 j 全为 0(每列"被击败"标记求和)。

O(n^2) 统计即可(也可利用传递性找"没输过"的队伍)。

复杂度:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    public int findChampion(int[][] grid) {
        int n = grid.length;
        for (int j = 0; j < n; j++) {
            boolean beaten = false;
            for (int i = 0; i < n; i++) {
                if (i != j && grid[i][j] == 1) {
                    beaten = true;
                    break;
                }
            }
            if (!beaten) {
                return j;
            }
        }
        return -1;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:按定义检查每个队伍是否被任意队强于
    private static int brute(int[][] grid) {
        int n = grid.length;
        for (int j = 0; j < n; j++) {
            boolean beaten = false;
            for (int i = 0; i < n; i++) {
                if (grid[i][j] == 1) {
                    beaten = true;
                    break;
                }
            }
            if (!beaten) {
                return j;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[][]{{0, 1}, {0, 0}}, 0);
        check(s, new int[][]{{0, 0, 1}, {1, 0, 1}, {0, 0, 0}}, 1);

        // 随机对拍:随机生成满足传递性的强弱矩阵(全序)
        Random rnd = new Random(12);
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(6);
            // 随机排列决定强弱顺序
            int[] order = new int[n];
            for (int i = 0; i < n; i++) {
                order[i] = i;
            }
            // 洗牌
            for (int i = n - 1; i > 0; i--) {
                int j = rnd.nextInt(i + 1);
                int tmp = order[i];
                order[i] = order[j];
                order[j] = tmp;
            }
            int rank = -1;
            int[][] g = new int[n][n];
            for (int a = 0; a < n; a++) {
                for (int b = 0; b < n; b++) {
                    if (a == b) {
                        g[a][b] = 0;
                    } else {
                        // 在 order 中 rank 小的更强
                        int ra = -1, rb = -1;
                        for (int x = 0; x < n; x++) {
                            if (order[x] == a) {
                                ra = x;
                            }
                            if (order[x] == b) {
                                rb = x;
                            }
                        }
                        g[a][b] = ra < rb ? 1 : 0;
                    }
                }
            }
            for (int x = 0; x < n; x++) {
                if (order[x] == order[0]) {
                    rank = order[0];
                }
            }
            int exp = order[0]; // 最强(rank 最小)
            int got = s.findChampion(g);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[][] grid, int exp) {
        int got = s.findChampion(grid);
        int exp2 = brute(grid);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " grid="
                + Arrays.deepToString(grid) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

2924. Find Champion II(找到冠军 II)

题目描述

有 n 支队伍(0...n-1),每支队伍是 DAG 上的节点。edges[i] = `ui`, `vi` 表示 ui 队比 vi 队强。

若不存在强于 a 队的队伍,则 a 队是冠军。若存在唯一冠军则返回之;否则返回 -1。

示例 1

复制代码
n = 3, edges = [[0,1],[1,2]] -> 0

示例 2

复制代码
n = 4, edges = [[0,2],[1,3],[1,2]] -> -1

提示:

  • 1 <= n <= 100,0 <= m <= n*(n-1)/2
  • 输入保证强关系满足传递性(DAG 的偏序闭包)

解题思路

解题思路:入度统计

冠军 = 入度为 0 的节点(没有任何队伍强于它)。由于强弱满足传递性且无环,

入度为 0 的节点若唯一,则它就是冠军(它经传递链击败所有其他队)。

统计每个节点的入度,找出入度为 0 的节点集合,若恰有一个返回该节点,否则 -1。

复杂度:

  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    public int findChampion(int n, int[][] edges) {
        int[] indeg = new int[n];
        for (int[] e : edges) {
            indeg[e[1]]++;
        }
        int champion = -1;
        for (int i = 0; i < n; i++) {
            if (indeg[i] == 0) {
                if (champion != -1) {
                    return -1; // 多个入度 0,无唯一冠军
                }
                champion = i;
            }
        }
        return champion;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:入度统计(同结构,交叉验证)
    private static int brute(int n, int[][] edges) {
        int[] indeg = new int[n];
        for (int[] e : edges) {
            indeg[e[1]]++;
        }
        int champ = -1;
        for (int i = 0; i < n; i++) {
            if (indeg[i] == 0) {
                if (champ != -1) {
                    return -1;
                }
                champ = i;
            }
        }
        return champ;
    }

    // 生成满足传递偏序的 DAG:随机全序 + 部分边(保证唯一冠军)
    private static int[][] genOrder(int n, Random rnd) {
        int[] order = new int[n];
        for (int i = 0; i < n; i++) {
            order[i] = i;
        }
        for (int i = n - 1; i > 0; i--) {
            int j = rnd.nextInt(i + 1);
            int tmp = order[i];
            order[i] = order[j];
            order[j] = tmp;
        }
        // rank 小者更强;随机加边(若 a 强于 b 则 rank[a] < rank[b])
        java.util.List<int[]> edges = new java.util.ArrayList<>();
        for (int a = 0; a < n; a++) {
            for (int b = 0; b < n; b++) {
                if (a == b) {
                    continue;
                }
                int ra = -1, rb = -1;
                for (int x = 0; x < n; x++) {
                    if (order[x] == a) {
                        ra = x;
                    }
                    if (order[x] == b) {
                        rb = x;
                    }
                }
                if (ra < rb && rnd.nextInt(3) == 0) {
                    edges.add(new int[]{a, b});
                }
            }
        }
        return edges.toArray(new int[0][]);
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 3, new int[][]{{0, 1}, {1, 2}}, 0);
        check(s, 4, new int[][]{{0, 2}, {1, 3}, {1, 2}}, -1);
        check(s, 2, new int[][]{}, -1); // 无边,两个入度 0

        // 随机对拍:半数有唯一冠军
        Random rnd = new Random(55);
        for (int t = 0; t < 300; t++) {
            int n = 2 + rnd.nextInt(7);
            int[][] edges = genOrder(n, rnd);
            int exp = brute(n, edges);
            int got = s.findChampion(n, edges);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " edges="
                        + Arrays.deepToString(edges) + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int n, int[][] edges, int exp) {
        int got = s.findChampion(n, edges);
        int exp2 = brute(n, edges);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n=" + n
                + " edges=" + Arrays.deepToString(edges) + " got=" + got + " exp=" + exp
                + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n)

2925. Maximum Score After Applying Operations on a Tree(在树上执行操作以后得到的最大分数)

题目描述

有 n 个节点的无向树,根为 0。values[i] 为节点 i 的值。初始分数 0。

操作:选择节点 i,把 values[i] 加到分数上,并把 values[i] 置 0(一步完成)。

可执行任意次操作,要求操作完成后树是健康的:从根到任意叶子路径的节点值之和都不为 0。

返回可得到的最大分数。

示例 1

复制代码
edges = [[0,1],[0,2],[0,3],[2,4],[4,5]], values = [5,2,5,2,1,1] -> 11

示例 2

复制代码
edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], values = [20,10,9,7,4,3,5] -> 40

提示:

  • 2 <= n <= 2*10^4
  • 1 <= values[i] <= 10^9

解题思路

解题思路:树形 DP

对每个节点 u 维护它的子树"能被取走的最大分数",同时保证根到 u 子树内每条叶子

路径的节点值之和非 0。由于路径和是"前缀和 + 子树内"叠加,单独处理每个节点难。

换个角度:健康条件可拆成"根到每个叶子的路径上保留至少一个非 0 节点值"。

考虑对任意节点 u,其子树内的叶子集合 L(u)。分两类:

  • 若根到 u 的路径上已经有一个保留下的非 0 节点(前缀非 0 已满足),则 u 子树
    可以全部取走。
  • 否则 u 子树内部必须"在每条根-叶路径上保留至少一个节点"。
    dpu0:根到 u 的路径(不含 u)尚未有保留节点时,u 子树能取的最大分(须保证
    子树内每条根-叶路径 u 往下都"健康");
    dpu1:已保留(前缀非 0 已满足),u 子树分数可取总和。
    转移:
    dpu1 = sum(valuesv 可取走) + sum(dpv1)?不准------若前缀已满足,
    u 本身可取走(得 valuesu),且子树每个节点也全可取走:dpu1 = valuesu + sum(dpv1)。
    dpu0:u 还没被保留(此前路径和为 0),若取走 u,则 u 子树前缀非 0,
    子树全取:= valuesu + sum(dpv1);
    若不取 u,则每棵子树 v 须处于"未保留"状态:= sum(dpv0);
    两者取大。
    其中叶子节点(无孩子)dpu0:必须保留 u(不保留则路径和为 0 不健康),
    所以 dpu0 = valuesu(取走 u 得 valuesu 且保留;注意"取走"是加分并置 0,
    置 0 后路径和仍为 valuesu?不对:取走操作把值加到分数且值置 0,
    健康要求的是操作后路径和 != 0。叶子只有自己,若取走自己则剩 0,路径和为 0 不健康!
    因此叶子必须不取走,dpu0 = 0(保留 valuesu),且 dpu1 = valuesu(可取走)。
    修正:
    dpu1 = valuesu + sum(dpv1)(前缀满足,u 可取走,子树也全取)
    dpu0(u 的祖先无保留,u 子树须健康):
    取走 u:u 变 0,子树必须自己健康 -> valuesu + sum(dpv0)?取走 u 后子树
    根-叶路径仍需非 0,但 u 本身为 0 与"未保留"等价,子树须 dpv0。
    不取 u:保留 u(值仍为 valuesu 非 0,路径和已满足)-> sum(dpv1)。
    叶子:dpu0 = 0(必须不取),dpu1 = valuesu。
    答 = dp00(根无祖先,树整体健康)。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

class Solution {
    public long maximumScoreAfterOperations(int[][] edges, int[] values) {
        int n = values.length;
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }
        for (int[] e : edges) {
            g.get(e[0]).add(e[1]);
            g.get(e[1]).add(e[0]);
        }
        long[] dp0 = new long[n];
        long[] dp1 = new long[n];
        dfs(0, -1, g, values, dp0, dp1);
        return dp0[0];
    }

    private void dfs(int u, int parent, List<List<Integer>> g, int[] values, long[] dp0, long[] dp1) {
        boolean leaf = true;
        long sumDp1Children = 0; // sum dp1[v]
        long sumDp0Children = 0; // sum dp0[v]
        for (int v : g.get(u)) {
            if (v == parent) {
                continue;
            }
            leaf = false;
            dfs(v, u, g, values, dp0, dp1);
            sumDp1Children += dp1[v];
            sumDp0Children += dp0[v];
        }
        if (leaf) {
            dp0[u] = 0;      // 必须保留叶子值,不能取
            dp1[u] = values[u]; // 前缀已满足,可取走
            return;
        }
        dp1[u] = values[u] + sumDp1Children;   // 前缀满足,全部可取
        // 前缀未满足:取 u(u 变 0,子树自己健康)或 不取 u(保留 u,子树全取)
        long takeU = values[u] + sumDp0Children;
        long keepU = sumDp1Children;
        dp0[u] = Math.max(takeU, keepU);
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:2^n 枚举取/不取(小数据),检查每条根-叶路径和
    private static long brute(int[][] edges, int[] values) {
        int n = values.length;
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }
        for (int[] e : edges) {
            g.get(e[0]).add(e[1]);
            g.get(e[1]).add(e[0]);
        }
        long best = 0;
        outer:
        for (int mask = 0; mask < (1 << n); mask++) {
            long score = 0;
            int[] rem = values.clone();
            for (int i = 0; i < n; i++) {
                if ((mask >> i & 1) == 1) {
                    score += values[i];
                    rem[i] = 0;
                }
            }
            // 检查健康:每条根到叶子的路径和都必须非 0
            boolean ok = dfsHealth(0, -1, g, rem, 0);
            if (ok) {
                best = Math.max(best, score);
            }
        }
        return best;
    }

    // 返回"根到叶子路径和是否全非 0":pathSum 为根到 u 之前(不含 u)的路径和
    private static boolean dfsHealth(int u, int p, List<List<Integer>> g, int[] rem, long pathSum) {
        boolean leaf = true;
        long ns = pathSum + rem[u];
        for (int v : g.get(u)) {
            if (v == p) {
                continue;
            }
            leaf = false;
            if (!dfsHealth(v, u, g, rem, ns)) {
                return false;
            }
        }
        if (leaf) {
            return ns != 0;
        }
        return true;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[][]{{0, 1}, {0, 2}, {0, 3}, {2, 4}, {4, 5}}, new int[]{5, 2, 5, 2, 1, 1}, 11);
        check(s, new int[][]{{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}},
                new int[]{20, 10, 9, 7, 4, 3, 5}, 40);

        // 随机对拍
        Random rnd = new Random(61);
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(8);
            int[][] edges = new int[n - 1][2];
            for (int i = 1; i < n; i++) {
                edges[i - 1][0] = rnd.nextInt(i);
                edges[i - 1][1] = i;
            }
            int[] values = new int[n];
            for (int i = 0; i < n; i++) {
                values[i] = 1 + rnd.nextInt(8);
            }
            long exp = brute(edges, values);
            long got = s.maximumScoreAfterOperations(edges, values);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " edges=" + Arrays.deepToString(edges)
                        + " values=" + Arrays.toString(values) + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[][] edges, int[] values, long exp) {
        long got = s.maximumScoreAfterOperations(edges, values);
        long exp2 = brute(edges, values);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " values="
                + Arrays.toString(values) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

2926. Maximum Balanced Subsequence Sum(平衡子序列的最大和)

题目描述

给定下标从 0 开始的整数数组 nums。子序列 i0 < i1 < ... < ik-1 是平衡的,如果对

1, `k`-1 内所有 j 都有 nums[ij] - nums[ij-1] >= ij - ij-1。长度为 1 的子序列总是

平衡的。返回平衡子序列的最大元素和。

示例 1

复制代码
nums = [3,3,5,6] -> 14([3,5,6])

示例 2

复制代码
nums = [5,-1,-3,8] -> 13([5,8])

示例 3

复制代码
nums = [-2,-1] -> -1([-1])

提示:

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

解题思路

解题思路:关键变形 + 离散化 BIT

平衡条件 numsb - numsa >= b - a(b > a)等价于

numsb - b >= numsa - a。定义 keyi = numsi - i。

则 dpi = numsi + max{ dpj | j < i 且 keyj <= keyi }。

这是经典的"按 key 前缀最大值 DP",用值域树状数组(离散化 key)维护

以 key 为索引的 dp 前缀最大值,O(n log n) 求解。

由于 numsi 可为负,答案要取所有 dpi 的最大(且至少单个元素)。

复杂度:

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;
import java.util.TreeMap;

class Solution {
    public long maxBalancedSubsequenceSum(int[] nums) {
        int n = nums.length;
        long[] key = new long[n];
        long[] sorted = new long[n];
        for (int i = 0; i < n; i++) {
            key[i] = (long) nums[i] - i;
            sorted[i] = key[i];
        }
        Arrays.sort(sorted);
        int m = 1;
        for (int i = 1; i < n; i++) {
            if (sorted[i] != sorted[m - 1]) {
                sorted[m++] = sorted[i];
            }
        }
        // BIT:前缀最大值
        long[] bit = new long[m + 2];
        Arrays.fill(bit, Long.MIN_VALUE / 2);
        long ans = Long.MIN_VALUE / 2;
        for (int i = 0; i < n; i++) {
            int idx = lowerBound(sorted, m, key[i]) + 1;
            long best = query(bit, idx); // key <= key[i] 的最大 dp
            long dp = nums[i];           // 单元素子序列兜底
            if (best != Long.MIN_VALUE / 2) {
                dp = Math.max(dp, best + nums[i]);
            }
            if (dp > ans) {
                ans = dp;
            }
            update(bit, m, idx, dp);
        }
        return ans;
    }

    private int lowerBound(long[] sorted, int m, long v) {
        int lo = 0, hi = m;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (sorted[mid] < v) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        return lo;
    }

    private long query(long[] bit, int i) {
        long r = Long.MIN_VALUE / 2;
        for (; i > 0; i -= i & (-i)) {
            r = Math.max(r, bit[i]);
        }
        return r;
    }

    private void update(long[] bit, int m, int i, long v) {
        for (; i <= m; i += i & (-i)) {
            bit[i] = Math.max(bit[i], v);
        }
    }

    // ========== 本地 main 自测 ==========

    // 暴力对照:O(n^2) DP
    private static long brute(int[] nums) {
        int n = nums.length;
        long[] dp = new long[n];
        long ans = Long.MIN_VALUE / 2;
        for (int i = 0; i < n; i++) {
            dp[i] = nums[i];
            for (int j = 0; j < i; j++) {
                if ((long) nums[i] - nums[j] >= (long) i - j) {
                    dp[i] = Math.max(dp[i], dp[j] + nums[i]);
                }
            }
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{3, 3, 5, 6}, 14);
        check(s, new int[]{5, -1, -3, 8}, 13);
        check(s, new int[]{-2, -1}, -1);
        check(s, new int[]{1, 2, 3, 4, 5}, 15);

        // 随机对拍
        Random rnd = new Random(71);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(10);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = rnd.nextInt(21) - 10;
            }
            long exp = brute(a);
            long got = s.maxBalancedSubsequenceSum(a);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " nums=" + Arrays.toString(a)
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] nums, long exp) {
        long got = s.maxBalancedSubsequenceSum(nums);
        long exp2 = brute(nums);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " nums="
                + Arrays.toString(nums) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(n)

2927. Distribute Candies Among Children III(给小朋友们分糖果 III)

题目描述

将 n 颗糖果分给 3 位小朋友,每位小朋友得到不超过 limit 颗,返回总方案数。

示例 1

复制代码
n = 5, limit = 2 -> 3

示例 2

复制代码
n = 3, limit = 3 -> 10

提示:

  • 1 <= n <= 10^8
  • 1 <= limit <= 10^8

解题思路

解题思路:容斥公式

无上限的方案数 C(n+2, 2)(隔板法)。三位孩子各至多 limit:

用容斥减去至少一位孩子 > limit 的方案。设 x_i = 孩子 i 的糖果数:

总方案 = C(n+2,2) - C(3,1)*C(n-limit-1+2? 需仔细)

标准做法:把"超过 limit"看作给该孩子减 limit+1 后再隔板。

设 A_i = {孩子 i 拿 >= limit+1}。则

总数 = C(n+2,2)

  • 3 * C(n-(limit+1)+2, 2) (n >= limit+1 时)
  • 3 * C(n-2(limit+1)+2, 2) (n >= 2(limit+1) 时)
  • 1 * C(n-3(limit+1)+2, 2) (n >= 3(limit+1) 时)
    其中 C(t+2, 2) 定义为 t >= 0 时的值,t < 0 时为 0。n <= 1e8,long 安全。
    复杂度:
  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    public long distributeCandies(int n, int limit) {
        long total = comb2(n);
        long bad1 = comb2(n - (limit + 1));          // 至少 1 人超限
        long bad2 = comb2(n - 2L * (limit + 1));     // 至少 2 人超限
        long bad3 = comb2(n - 3L * (limit + 1));     // 3 人都超限
        return total - 3 * bad1 + 3 * bad2 - bad3;
    }

    // C(t + 2, 2) = (t+2)(t+1)/2,t < 0 时为 0
    private long comb2(long t) {
        if (t < 0) {
            return 0;
        }
        return (t + 2) * (t + 1) / 2;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:三层循环
    private static long brute(int n, int limit) {
        long cnt = 0;
        for (int a = 0; a <= n && a <= limit; a++) {
            for (int b = 0; b <= n && b <= limit; b++) {
                int c = n - a - b;
                if (c >= 0 && c <= limit) {
                    cnt++;
                }
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 5, 2, 3);
        check(s, 3, 3, 10);

        // 随机对拍(较大范围)
        Random rnd = new Random(9);
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(150);
            int limit = 1 + rnd.nextInt(150);
            long exp = brute(n, limit);
            long got = s.distributeCandies(n, limit);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " limit=" + limit
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int n, int limit, long exp) {
        long got = s.distributeCandies(n, limit);
        long exp2 = brute(n, limit);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n=" + n
                + " limit=" + limit + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2928. Distribute Candies Among Children I(给小朋友们分糖果 I)

题目描述

将 n 颗糖果分给 3 位小朋友,每位小朋友得到不超过 limit 颗,返回总方案数。

(孩子是有编号的,(a,b,c) 与 (b,a,c) 视为不同方案)

示例 1

复制代码
n = 5, limit = 2 -> 3

示例 2

复制代码
n = 3, limit = 3 -> 10

提示:

  • 1 <= n <= 50
  • 1 <= limit <= 50

解题思路

解题思路:枚举

n <= 50,直接枚举第一位小朋友拿 0...min(n, limit) 颗,第二位拿 0...min(n-a, limit),

第三位拿剩下的 n-a-b,若不超过 limit 则计数。O(n^2)。

复杂度:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    public int distributeCandies(int n, int limit) {
        int ans = 0;
        for (int a = 0; a <= limit && a <= n; a++) {
            for (int b = 0; b <= limit && b <= n - a; b++) {
                int c = n - a - b;
                if (c >= 0 && c <= limit) {
                    ans++;
                }
            }
        }
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:三层循环
    private static int brute(int n, int limit) {
        int cnt = 0;
        for (int a = 0; a <= n && a <= limit; a++) {
            for (int b = 0; b <= n && b <= limit; b++) {
                for (int c = 0; c <= n && c <= limit; c++) {
                    if (a + b + c == n) {
                        cnt++;
                    }
                }
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 5, 2, 3);
        check(s, 3, 3, 10);

        // 随机对拍
        Random rnd = new Random(3);
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(40);
            int limit = 1 + rnd.nextInt(40);
            int exp = brute(n, limit);
            int got = s.distributeCandies(n, limit);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " limit=" + limit
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int n, int limit, int exp) {
        int got = s.distributeCandies(n, limit);
        int exp2 = brute(n, limit);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n=" + n
                + " limit=" + limit + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

2929. Distribute Candies Among Children II(给小朋友们分糖果 II)

题目描述

将 n 颗糖果分给 3 位小朋友,每位小朋友得到不超过 limit 颗,返回总方案数。

示例 1

复制代码
n = 5, limit = 2 -> 3

示例 2

复制代码
n = 3, limit = 3 -> 10

提示:

  • 1 <= n <= 10^6
  • 1 <= limit <= 10^6

解题思路

解题思路:枚举第一人 + 数学计数

枚举第一位孩子拿 a 颗(0...min(n, limit)),剩余 r = n - a 分给两位孩子,

每人 0...limit:方案数 = 两条上限约束的交叠计数 = f® - g®,其中

f® = max(0, min(r, limit) - max(0, r - limit) + 1),即第二位能取的范围长度;

化简为区间交叠公式:lo = max(0, r - limit),hi = min(r, limit),

if (lo > hi) 0 else hi - lo + 1。O(min(n, limit))。

复杂度:

  • 时间复杂度:O(min(n, limit))
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    public long distributeCandies(int n, int limit) {
        long ans = 0;
        for (int a = 0; a <= limit && a <= n; a++) {
            int r = n - a;
            long lo = Math.max(0, (long) r - limit);
            long hi = Math.min((long) r, limit);
            if (lo <= hi) {
                ans += hi - lo + 1;
            }
        }
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:三层循环
    private static long brute(int n, int limit) {
        long cnt = 0;
        for (int a = 0; a <= n && a <= limit; a++) {
            for (int b = 0; b <= n && b <= limit; b++) {
                int c = n - a - b;
                if (c >= 0 && c <= limit) {
                    cnt++;
                }
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 5, 2, 3);
        check(s, 3, 3, 10);

        // 随机对拍
        Random rnd = new Random(5);
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(60);
            int limit = 1 + rnd.nextInt(60);
            long exp = brute(n, limit);
            long got = s.distributeCandies(n, limit);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " limit=" + limit
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int n, int limit, long exp) {
        long got = s.distributeCandies(n, limit);
        long exp2 = brute(n, limit);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n=" + n
                + " limit=" + limit + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(min(n, limit))
  • 空间复杂度:O(1)

2930. Number of Strings Which Can Be Rearranged to Contain Substring(重新排列后包含指定子字符串的字符串数目)

题目描述

给整数 n。若长度为 n 的小写字母字符串 s,其字符重新排列后能包含子字符串 "leet",

则 s 是好字符串。返回长度为 n 的好字符串总数,对 1e9+7 取模。

示例 1

复制代码
n = 4 -> 12

示例 2

复制代码
n = 10 -> 83943898

提示:

  • 1 <= n <= 10^5

解题思路

解题思路:补集 + 容斥

s 可重排含 "leet" ⇔ s 中至少含 2 个 'e'、1 个 'l'、1 个 't'。

总串数 = 26^n。坏串 = (l 为 0) ∪ (t 为 0) ∪ (e 的个数 <= 1)。

设 A={l=0},B={t=0},C={e<=1}:

|A| = |B| = 25^n

|C| = 25^n + n·25^(n-1)

|AB| = 24^n

|AC| = |BC| = 24^n + n·24^(n-1)

|ABC| = 23^n + n·23^(n-1)

bad = |A|+|B|+|C| - |AB|-|AC|-|BC| + |ABC|

good = 26^n - bad。

用快速幂直接算(组合系数就是 3、2n、n 等),O(log n)。

复杂度:

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    private static final int MOD = 1_000_000_007;

    public int stringCount(int n) {
        long total = pow(26, n);
        long a = 3 * pow(25, n) % MOD;                  // |A|+|B|+|C| 中 25^n 部分(3 项)
        long nc = n * pow(25, n - 1) % MOD;             // |C| 的 e=1 项
        long ab = 3 * pow(24, n) % MOD;                 // |AB|、|AC|、|BC| 的 24^n 部分
        long ac = 2L * n % MOD * pow(24, n - 1) % MOD;  // |AC|+|BC| 的 e=1 项
        long abc0 = pow(23, n);                         // |ABC| 的 e=0 项
        long abc1 = n * pow(23, n - 1) % MOD;           // |ABC| 的 e=1 项
        long bad = (a + nc - ab - ac + abc0 + abc1) % MOD;
        bad = (bad + MOD) % MOD;
        return (int) ((total - bad + MOD) % MOD);
    }

    private static long pow(long a, int b) {
        if (b < 0) {
            return 0;
        }
        long r = 1;
        a %= MOD;
        while (b > 0) {
            if ((b & 1) == 1) {
                r = r * a % MOD;
            }
            a = a * a % MOD;
            b >>= 1;
        }
        return r;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:枚举 l/t/e 数量 + 多重集排列(小 n)
    private static long brute(int n) {
        long ans = 0;
        for (int l = 1; l <= n; l++) {
            for (int t = 1; l + t <= n; t++) {
                for (int e = 2; e + l + t <= n; e++) {
                    int rest = n - l - t - e;
                    long ways = multinomial(n, l, t, e, rest);
                    ans = (ans + ways) % MOD;
                }
            }
        }
        return ans;
    }

    // 多重集排列:位置放 l/t/e 各 1 类 + 其余 rest 位置填 23 类字母
    private static long multinomial(int n, int l, int t, int e, int rest) {
        long res = 1;
        int rem = n;
        res = res * C(rem, l) % MOD;
        rem -= l;
        res = res * C(rem, t) % MOD;
        rem -= t;
        res = res * C(rem, e) % MOD;
        rem -= e;
        res = res * pow(23, rem) % MOD;
        return res;
    }

    private static long C(int n, int k) {
        if (k < 0 || k > n) {
            return 0;
        }
        long r = 1;
        for (int i = 0; i < k; i++) {
            r = r * (n - i) / (i + 1);
        }
        return r % MOD;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 4, 12);
        check(s, 10, 83943898);
        check(s, 1, 0);
        check(s, 2, 0);
        check(s, 3, 0);

        // 随机对拍(小 n)
        Random rnd = new Random(13);
        for (int t = 0; t < 60; t++) {
            int n = 4 + rnd.nextInt(8);
            long exp = brute(n);
            long got = s.stringCount(n);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n=" + n + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int n, long exp) {
        long got = s.stringCount(n);
        long exp2 = brute(n);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n=" + n
                + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

2931. Maximum Spending After Buying Items(购买物品的最大开销)

题目描述

给定 mn 矩阵 values,第 i 个商店的物品按价值非递增排序(values[i][j] >= values[i][j+1])。
第 d 天可选择一个商店购买该店还没买过的最右物品 j,开销为 values[i][j] * d。
返回购买全部 m
n 件物品的最大总开销。

示例 1

复制代码
values = [[8,5,2],[6,4,1],[9,7,3]] -> 285

示例 2

复制代码
values = [[10,8,6,4,2],[9,7,5,3,2]] -> 386

提示:

  • 1 <= m <= 10,1 <= n <= 10^4
  • 1 <= values[i][j] <= 10^6,每行非递增

解题思路

解题思路:贪心 + 小根堆

每天买到的物品价值 v 会被乘上"当前天数 d"。为了最大化总和,价值越小的物品越该

在越早的天数购买(乘数小),价值大的留给后面的天数。所有物品都要买,

因此把每天的"待购物品中价值最小"的拿出来买即可(小根堆,按 value 排序,

购买该商店当前最右物品后,把该商店左侧下一件(更大价值)放入堆)。

复杂度 O(mn log m)。

复杂度:

  • 时间复杂度:O(mnlog m)
  • 空间复杂度:O(m)

代码实现

java 复制代码
import java.util.PriorityQueue;
import java.util.Random;

class Solution {
    public long maxSpending(int[][] values) {
        int m = values.length, n = values[0].length;
        // 每个商店当前指向 0..n-1 中最右未买的列(初始 n-1)------实际用小根堆存 (value, row)
        PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0]));
        int[] col = new int[m]; // 每个商店当前最右未买列
        for (int i = 0; i < m; i++) {
            col[i] = n - 1;
            pq.add(new long[]{values[i][n - 1], i});
        }
        long ans = 0;
        int total = m * n;
        for (int day = 1; day <= total; day++) {
            long[] top = pq.poll();
            long v = top[0];
            int row = (int) top[1];
            ans += v * day;
            if (col[row] > 0) {
                col[row]--;
                pq.add(new long[]{values[row][col[row]], row});
            }
        }
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:模拟每天在所有商店最右未买物品中选价值最小的
    private static long brute(int[][] values) {
        int m = values.length, n = values[0].length;
        int[] col = new int[m];
        for (int i = 0; i < m; i++) {
            col[i] = n - 1;
        }
        long ans = 0;
        int total = m * n;
        for (int day = 1; day <= total; day++) {
            int bestRow = -1;
            long bestV = Long.MAX_VALUE;
            for (int i = 0; i < m; i++) {
                if (col[i] >= 0 && values[i][col[i]] < bestV) {
                    bestV = values[i][col[i]];
                    bestRow = i;
                }
            }
            ans += bestV * day;
            col[bestRow]--;
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[][]{{8, 5, 2}, {6, 4, 1}, {9, 7, 3}}, 285);
        check(s, new int[][]{{10, 8, 6, 4, 2}, {9, 7, 5, 3, 2}}, 386);

        // 随机对拍(每行非递增)
        Random rnd = new Random(17);
        for (int t = 0; t < 200; t++) {
            int m = 1 + rnd.nextInt(4);
            int n = 1 + rnd.nextInt(6);
            int[][] v = new int[m][n];
            for (int i = 0; i < m; i++) {
                int cur = 2 + rnd.nextInt(10);
                for (int j = 0; j < n; j++) {
                    v[i][j] = cur;
                    cur = Math.max(1, cur - rnd.nextInt(3));
                }
            }
            long exp = brute(v);
            long got = s.maxSpending(v);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[][] v, long exp) {
        long got = s.maxSpending(v);
        long exp2 = brute(v);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " got=" + got
                + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(mnlog m)
  • 空间复杂度:O(m)

2932. Maximum Strong Pair XOR I(找出强数对的最大异或值 I)

题目描述

给定下标从 0 开始的整数数组 nums。若 |x - y| <= min(x, y),则 (x, y) 是强数对。

返回 nums 中所有强数对的最大异或值(可同一整数选两次)。

示例 1

复制代码
nums = [1,2,3,4,5] -> 7(3 XOR 4)

示例 2

复制代码
nums = [10,100] -> 0

示例 3

复制代码
nums = [5,6,25,30] -> 7(25 XOR 30)

提示:

  • 1 <= nums.length <= 50
  • 1 <= nums[i] <= 100(本题 I 的小数据版,用双重循环即可)

解题思路

解题思路:枚举

双重循环枚举所有下标对(含 i==j),验证强数对条件取最大异或。O(n^2)。

复杂度:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    public int maximumStrongPairXor(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int x = nums[i], y = nums[j];
                if (Math.abs(x - y) <= Math.min(x, y)) {
                    ans = Math.max(ans, x ^ y);
                }
            }
        }
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:同结构但用集合两两验证
    private static int brute(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (Math.abs(nums[i] - nums[j]) <= Math.min(nums[i], nums[j])) {
                    ans = Math.max(ans, nums[i] ^ nums[j]);
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{1, 2, 3, 4, 5}, 7);
        check(s, new int[]{10, 100}, 0);
        check(s, new int[]{5, 6, 25, 30}, 7);

        // 随机对拍
        Random rnd = new Random(23);
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(10);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = 1 + rnd.nextInt(100);
            }
            int exp = brute(a);
            int got = s.maximumStrongPairXor(a);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " nums=" + Arrays.toString(a)
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] nums, int exp) {
        int got = s.maximumStrongPairXor(nums);
        int exp2 = brute(nums);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " nums="
                + Arrays.toString(nums) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

2933. High-Access Employees(高访问员工)

题目描述

给定二维字符串数组 access_times,access_times[i][0] 为员工姓名,access_times[i][1]

为访问时间(四位数字,24 小时制,如 "0800"、"2250")。若员工在同一小时内访问系统

3 次或以上,则为高访问员工。时间间隔正好相差一小时不视为同一小时。

返回高访问员工名单(按字典序排序)。

示例 1

复制代码
access_times = [["a","0549"],["h","0452"],["j","0501"],["a","0640"]] -> ["a"]? 自行按提示

提示:

  • 1 <= access_times.length <= 100
  • 员工姓名长度 1...10,时间合法

解题思路

解题思路:按人分组 + 排序 + 滑窗

对每个员工收集访问时间(转为分钟数)排序。若存在窗口长度为 3 且

timesi+2 - timesi < 60(严格小于 60 才属于同一小时),则该员工高访问。

(注:正好 60 分钟不视为同一小时------条件为 < 60。)

复杂度:

  • 时间复杂度:O(n log n)(排序)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

class Solution {
    public List<String> findHighAccessEmployees(List<List<String>> access_times) {
        Map<String, List<Integer>> map = new HashMap<>();
        for (List<String> e : access_times) {
            String name = e.get(0);
            String t = e.get(1);
            int min = Integer.parseInt(t.substring(0, 2)) * 60 + Integer.parseInt(t.substring(2));
            map.computeIfAbsent(name, x -> new ArrayList<>()).add(min);
        }
        List<String> ans = new ArrayList<>();
        for (Map.Entry<String, List<Integer>> e : map.entrySet()) {
            List<Integer> list = e.getValue();
            Collections.sort(list);
            boolean high = false;
            for (int i = 0; i + 2 < list.size(); i++) {
                if (list.get(i + 2) - list.get(i) < 60) {
                    high = true;
                    break;
                }
            }
            if (high) {
                ans.add(e.getKey());
            }
        }
        Collections.sort(ans);
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:同一人任意三个时间两两检查(任意两时间差 < 60 且都发生则计数)
    // 正确判定:存在三个时间,其中 max-min < 60。
    private static List<String> brute(List<List<String>> access_times) {
        Map<String, List<Integer>> map = new HashMap<>();
        for (List<String> e : access_times) {
            String t = e.get(1);
            int min = Integer.parseInt(t.substring(0, 2)) * 60 + Integer.parseInt(t.substring(2));
            map.computeIfAbsent(e.get(0), x -> new ArrayList<>()).add(min);
        }
        List<String> ans = new ArrayList<>();
        for (Map.Entry<String, List<Integer>> e : map.entrySet()) {
            List<Integer> l = e.getValue();
            boolean ok = false;
            for (int i = 0; i < l.size() && !ok; i++) {
                for (int j = i + 1; j < l.size() && !ok; j++) {
                    for (int k = j + 1; k < l.size(); k++) {
                        int mx = Math.max(l.get(i), Math.max(l.get(j), l.get(k)));
                        int mn = Math.min(l.get(i), Math.min(l.get(j), l.get(k)));
                        if (mx - mn < 60) {
                            ok = true;
                            break;
                        }
                    }
                }
            }
            if (ok) {
                ans.add(e.getKey());
            }
        }
        Collections.sort(ans);
        return ans;
    }

    private static String fmt(int min) {
        return String.format("%02d%02d", min / 60, min % 60);
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例(部分来自题目)
        check(s, Arrays.asList(
                Arrays.asList("a", "0549"), Arrays.asList("h", "0452"),
                Arrays.asList("j", "0501"), Arrays.asList("a", "0640")),
                new String[]{});
        check(s, Arrays.asList(
                Arrays.asList("a", "0509"), Arrays.asList("a", "0512"),
                Arrays.asList("a", "0515"), Arrays.asList("b", "1000"),
                Arrays.asList("b", "1020")),
                new String[]{"a"});

        // 随机对拍
        Random rnd = new Random(31);
        String[] names = {"x", "y", "z", "w"};
        for (int t = 0; t < 300; t++) {
            int n = 3 + rnd.nextInt(10);
            List<List<String>> acc = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                String name = names[rnd.nextInt(4)];
                int min = rnd.nextInt(24 * 60);
                acc.add(Arrays.asList(name, fmt(min)));
            }
            List<String> exp = brute(acc);
            List<String> got = s.findHighAccessEmployees(acc);
            if (!exp.equals(got)) {
                System.out.println("FAIL t=" + t + " acc=" + acc + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, List<List<String>> acc, String[] expArr) {
        List<String> exp = Arrays.asList(expArr);
        List<String> got = s.findHighAccessEmployees(acc);
        System.out.println((got.equals(exp) ? "PASS" : "FAIL") + " got=" + got + " exp=" + exp);
    }
}

复杂度分析

  • 时间复杂度:O(n log n)(排序)
  • 空间复杂度:O(n)

2934. Minimum Operations to Maximize Last Elements in Arrays(最大化数组末位元素的最少操作次数)

题目描述

给定两个长度都是 n 的整数数组 nums1 和 nums2。每次操作可选择下标 i 交换

nums1[i] 与 nums2[i]。求使两端末位分别为各自数组最大值的条件成立的最小操作次数;

无法满足返回 -1。

示例 1

复制代码
nums1 = [1,2,7], nums2 = [4,5,3] -> 1

示例 2

复制代码
nums1 = [2,3,4,5,9], nums2 = [8,8,4,4,4] -> 2

示例 3

复制代码
nums1 = [1,5,4], nums2 = [2,5,3] -> -1

提示:

  • 1 <= n == nums1.length == nums2.length <= 1000
  • 1 <= nums1[i], nums2[i] <= 10^9

解题思路

解题思路:枚举末两位是否交换

末位 n-1 处是否交换只影响其自身值。分两类情况:

  1. 不交换末位:a = nums1n-1, b = nums2n-1 保持不变;
  2. 交换末位:a = nums2n-1, b = nums1n-1。
    对每种情况,扫描 0...n-2 位置:若 nums1i > a 或 nums2i > b(不交换时越界),
    尝试交换位置 i(交换后需 nums2i <= a 且 nums1i <= b);
    若无法满足则此情况非法。统计两种情况的合法最小交换次数,取小。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    public int minOperations(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int ans1 = solve(nums1, nums2, false, n);
        int ans2 = solve(nums1, nums2, true, n);
        int best = Math.min(ans1, ans2);
        return best == Integer.MAX_VALUE / 2 ? -1 : best;
    }

    private int solve(int[] n1, int[] n2, boolean swapLast, int n) {
        int a = swapLast ? n2[n - 1] : n1[n - 1];
        int b = swapLast ? n1[n - 1] : n2[n - 1];
        int cnt = swapLast ? 1 : 0;
        for (int i = 0; i < n - 1; i++) {
            int x = n1[i], y = n2[i];
            if (x <= a && y <= b) {
                continue;
            }
            // 尝试交换
            int nx = y, ny = x;
            if (nx <= a && ny <= b) {
                cnt++;
            } else {
                return Integer.MAX_VALUE / 2;
            }
        }
        return cnt;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:2^(n-1) 枚举除末位外每个位置是否交换,配合末位两态
    private static int brute(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int best = Integer.MAX_VALUE;
        for (int swapLast = 0; swapLast < 2; swapLast++) {
            for (int mask = 0; mask < (1 << (n - 1)); mask++) {
                int[] a1 = nums1.clone();
                int[] a2 = nums2.clone();
                if (swapLast == 1) {
                    int t = a1[n - 1];
                    a1[n - 1] = a2[n - 1];
                    a2[n - 1] = t;
                }
                for (int i = 0; i < n - 1; i++) {
                    if ((mask >> i & 1) == 1) {
                        int t = a1[i];
                        a1[i] = a2[i];
                        a2[i] = t;
                    }
                }
                int mx1 = Integer.MIN_VALUE, mx2 = Integer.MIN_VALUE;
                for (int x : a1) {
                    mx1 = Math.max(mx1, x);
                }
                for (int x : a2) {
                    mx2 = Math.max(mx2, x);
                }
                if (a1[n - 1] == mx1 && a2[n - 1] == mx2) {
                    int cnt = swapLast + Integer.bitCount(mask);
                    best = Math.min(best, cnt);
                }
            }
        }
        return best == Integer.MAX_VALUE ? -1 : best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{1, 2, 7}, new int[]{4, 5, 3}, 1);
        check(s, new int[]{2, 3, 4, 5, 9}, new int[]{8, 8, 4, 4, 4}, 2);
        check(s, new int[]{1, 5, 4}, new int[]{2, 5, 3}, -1);

        // 随机对拍
        Random rnd = new Random(37);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(7);
            int[] a = new int[n], b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = 1 + rnd.nextInt(8);
                b[i] = 1 + rnd.nextInt(8);
            }
            int exp = brute(a, b);
            int got = s.minOperations(a, b);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " n1=" + Arrays.toString(a)
                        + " n2=" + Arrays.toString(b) + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] n1, int[] n2, int exp) {
        int got = s.minOperations(n1, n2);
        int exp2 = brute(n1, n2);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " n1="
                + Arrays.toString(n1) + " n2=" + Arrays.toString(n2) + " got=" + got
                + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2935. Maximum Strong Pair XOR II(找出强数对的最大异或值 II)

题目描述

给定下标从 0 开始的整数数组 nums。若 |x - y| <= min(x, y),则 (x, y) 是强数对。

返回 nums 中所有强数对的最大异或值(可同一整数选两次)。

示例 1

复制代码
nums = [1,2,3,4,5] -> 7(3 XOR 4)

示例 2

复制代码
nums = [10,100] -> 0

示例 3

复制代码
nums = [500,520,2500,3000] -> 1020(500 XOR 520)

提示:

  • 1 <= nums.length <= 5*10^4
  • 1 <= nums[i] <= 2^20 - 1

解题思路

解题思路:排序 + 滑动窗口 + 字典树

设 a <= b,强数对条件 |a-b| <= min(a,b) 即 b - a <= a,即 b <= 2a。

将数组升序排序,遍历较大者 b = numsi(保证下标在窗口内),维护窗口内所有

满足 2*numsl >= b 的 a。由于 b 单调不减,窗口左边界 l 也单调右移,

每个元素恰插入字典树一次、删除一次。枚举所有元素做 maxXor 查询取最大。

复杂度 O(n log V),V = 2^21。

复杂度:

  • 时间复杂度:O(n log V)
  • 空间复杂度:O(n log V + n)(字典树节点 + 排序)

代码实现

java 复制代码
import java.util.Arrays;
import java.util.Random;

class Solution {
    private static final int BITS = 21;

    public int maximumStrongPairXor(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int maxNodes = n * BITS + 2;
        int[][] ch = new int[maxNodes][2];
        int[] cnt = new int[maxNodes];
        int nodeCnt = 1; // 根为 0,子节点从 1 开始分配
        int l = 0;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            // 插入当前较大者 b = nums[i]
            nodeCnt = trieInsert(ch, cnt, nodeCnt, nums[i]);
            // 移除所有 a 使 2a < b(a 太小不构成强数对)
            while (2L * nums[l] < nums[i]) {
                trieRemove(ch, cnt, nums[l]);
                l++;
            }
            ans = Math.max(ans, trieQuery(ch, cnt, nums[i]));
        }
        return ans;
    }

    private int trieInsert(int[][] ch, int[] cnt, int nodeCnt, int v) {
        int node = 0;
        cnt[0]++;
        for (int b = BITS - 1; b >= 0; b--) {
            int bit = (v >> b) & 1;
            if (ch[node][bit] == 0) {
                ch[node][bit] = nodeCnt++;
            }
            node = ch[node][bit];
            cnt[node]++;
        }
        return nodeCnt;
    }

    private void trieRemove(int[][] ch, int[] cnt, int v) {
        int node = 0;
        cnt[0]--;
        for (int b = BITS - 1; b >= 0; b--) {
            int bit = (v >> b) & 1;
            node = ch[node][bit];
            cnt[node]--;
        }
    }

    // 查询与 v 异或最大的已存在数
    private int trieQuery(int[][] ch, int[] cnt, int v) {
        int node = 0;
        int res = 0;
        for (int b = BITS - 1; b >= 0; b--) {
            int bit = (v >> b) & 1;
            int want = bit ^ 1;
            if (ch[node][want] != 0 && cnt[ch[node][want]] > 0) {
                res |= 1 << b;
                node = ch[node][want];
            } else {
                node = ch[node][bit];
            }
        }
        return res;
    }

    // ========== 本地 main 自测 ==========

    // 暴力对照
    private static int brute(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (Math.abs(nums[i] - nums[j]) <= Math.min(nums[i], nums[j])) {
                    ans = Math.max(ans, nums[i] ^ nums[j]);
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{1, 2, 3, 4, 5}, 7);
        check(s, new int[]{10, 100}, 0);
        check(s, new int[]{500, 520, 2500, 3000}, 1020);

        // 随机对拍(含重复值)
        Random rnd = new Random(41);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(12);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = 1 + rnd.nextInt(300);
            }
            int exp = brute(a);
            int got = s.maximumStrongPairXor(a);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " nums=" + Arrays.toString(a)
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] nums, int exp) {
        int got = s.maximumStrongPairXor(nums);
        int exp2 = brute(nums);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " nums="
                + Arrays.toString(nums) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n log V)
  • 空间复杂度:O(n log V + n)(字典树节点 + 排序)

2936. Number of Equal Numbers Blocks(包含相等值数字块的数量)

题目描述

给定很大(可达 10^15)的数组 nums,同值相邻。提供 BigArray 接口:

int at(long index);long size()

将数组分成最大的块,每个块内值相等,返回块数。

示例 1

复制代码
nums = [3,3,3,3,3] -> 1

示例 2

复制代码
nums = [1,1,1,3,9,9,9,2,10,10] -> 5

示例 3

复制代码
nums = [1,2,3,4,5,6,7] -> 7

提示:

  • 1 <= nums.length <= 10^15
  • 1 <= nums[i] <= 10^9
  • 相同值相邻

解题思路

解题思路:二分跳跃

从 pos = 0 开始:记录 val = at(pos);用二分查找第一个不等于 val 的位置

(即值为 val 的块右边界),块数 +1,pos = 该边界;重复直到 pos == size。

二分查找用标准 lowerBound 变体(找第一个 at(mid) != val)。

at() 调用次数 O(块数 * log size),块数可能很大,但题目现实测试规模可控。

复杂度:

  • 时间复杂度:O(块数 * log size)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

class Solution {
    public int countBlocks(BigArray nums) {
        long n = nums.size();
        long pos = 0;
        int blocks = 0;
        while (pos < n) {
            int val = nums.at(pos);
            // 二分找第一个 != val 的位置 r(该块为 [pos, r))
            long lo = pos, hi = n;
            while (lo < hi) {
                long mid = (lo + hi) >>> 1;
                if (nums.at(mid) == val) {
                    lo = mid + 1;
                } else {
                    hi = mid;
                }
            }
            blocks++;
            pos = lo;
        }
        return blocks;
    }

    // ========== 本地 main 自测 ==========

    // BigArray 测试实现
    static class BigArray {
        long[] data;

        BigArray(int[] arr) {
            data = new long[arr.length];
            for (int i = 0; i < arr.length; i++) {
                data[i] = arr[i];
            }
        }

        int at(long index) {
            return (int) data[(int) index];
        }

        long size() {
            return data.length;
        }
    }

    // 独立暴力:线性扫描
    private static int brute(int[] arr) {
        int cnt = 0;
        for (int i = 0; i < arr.length; i++) {
            if (i == 0 || arr[i] != arr[i - 1]) {
                cnt++;
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{3, 3, 3, 3, 3}, 1);
        check(s, new int[]{1, 1, 1, 3, 9, 9, 9, 2, 10, 10}, 5);
        check(s, new int[]{1, 2, 3, 4, 5, 6, 7}, 7);

        // 随机对拍(严格符合"每个值只形成一个连续块"的性质)
        Random rnd = new Random(47);
        for (int t = 0; t < 300; t++) {
            // 先随机分块:块数 c,块长度随机,每块的值全局唯一
            int[] lenRun = new int[8];
            int total = 0;
            int c = 0;
            while (total < 15 && c < 8) {
                int L = 1 + rnd.nextInt(4);
                lenRun[c] = L;
                total += L;
                c++;
            }
            int[] a = new int[total];
            java.util.Set<Integer> usedVal = new java.util.HashSet<>();
            int idx = 0;
            for (int blk = 0; blk < c; blk++) {
                int v;
                do {
                    v = 1 + rnd.nextInt(30);
                } while (usedVal.contains(v));
                usedVal.add(v);
                for (int i = 0; i < lenRun[blk]; i++) {
                    a[idx++] = v;
                }
            }
            int exp = brute(a);
            int got = s.countBlocks(new BigArray(a));
            if (exp != got) {
                System.out.println("FAIL t=" + t + " a=" + Arrays.toString(a)
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] arr, int exp) {
        int got = s.countBlocks(new BigArray(arr));
        int exp2 = brute(arr);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " arr="
                + Arrays.toString(arr) + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(块数 * log size)
  • 空间复杂度:O(1)

2937. Make Three Strings Equal(使三个字符串相等)

题目描述

给定三个字符串 s1、s2、s3。每次操作可选择其中一个长度至少为 2 的字符串,

删除其最右字符。若能使三者相等,返回最小操作次数;否则 -1。

示例 1

复制代码
s1="abc", s2="abb", s3="ab" -> 2(s1、s2 各删一次变 "ab")

示例 2

复制代码
s1="dac", s2="bac", s3="cac" -> -1(首字符不同,永远无法相等)

提示:

  • 1 <= s1.length, s2.length, s3.length <= 100

解题思路

解题思路:找公共前缀

三串最终相等只能是它们三者的公共前缀。找到三者最长公共前缀长度 L,

则所需操作数 = (len1 - L) + (len2 - L) + (len3 - L)。若 L == 0 且存在非空串

则不可行返回 -1(实际 L0 说明首字符不同,无公共非空前缀 → -1;
空串也是合法的"相等结果"?操作要求选长度>=2 的串,最终可为空串。
定义:若三者公共前缀为 L,直接把每串删到前缀长度即可。
若 L0,是否 -1?例:s1="a", s2="a", s3="a" -> 三者已相等,L=1。

例:s1="", ... 字符串长度 >=1。若三串首字符不同,无法为"非空"公共前缀,

但可为空串吗?空串是合法的最终状态(只要操作后串可为空)。

官方解答:最大公共前缀为 0 时返回 -1(除非三串已经都为空,不可能,长度>=1)。

所以:L=0 -> -1;否则返回 (len1+len2+len3) - 3L。

复杂度:

  • 时间复杂度:O(min(len))
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    public int findMinimumOperations(String s1, String s2, String s3) {
        int minLen = Math.min(s1.length(), Math.min(s2.length(), s3.length()));
        int L = 0;
        while (L < minLen && s1.charAt(L) == s2.charAt(L) && s2.charAt(L) == s3.charAt(L)) {
            L++;
        }
        if (L == 0) {
            return -1;
        }
        return s1.length() - L + s2.length() - L + s3.length() - L;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:枚举最终相等串长度(只能为公共前缀),若 L=0 且三串都被删空需特殊处理
    private static int brute(String s1, String s2, String s3) {
        // 最终相等只能是三者的公共前缀(或全空)。操作可删至任意长度,
        // 因此最终串 = 公共前缀 [0, L),只需检查每个 L 是否可行并计操作数。
        int maxL = Math.min(s1.length(), Math.min(s2.length(), s3.length()));
        // 若三串完全相同则 0 次
        if (s1.equals(s2) && s2.equals(s3)) {
            return 0;
        }
        // 求最大公共前缀长度
        int L = 0;
        while (L < maxL && s1.charAt(L) == s2.charAt(L) && s2.charAt(L) == s3.charAt(L)) {
            L++;
        }
        if (L == 0) {
            return -1; // 首字符不同,无法相等
        }
        return s1.length() - L + s2.length() - L + s3.length() - L;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, "abc", "abb", "ab", 2);
        check(s, "dac", "bac", "cac", -1);

        // 随机对拍
        Random rnd = new Random(53);
        char[] cs = {'a', 'b', 'c'};
        for (int t = 0; t < 500; t++) {
            int len1 = 1 + rnd.nextInt(7);
            int len2 = 1 + rnd.nextInt(7);
            int len3 = 1 + rnd.nextInt(7);
            StringBuilder a = new StringBuilder(), b = new StringBuilder(), c = new StringBuilder();
            for (int i = 0; i < len1; i++) {
                a.append(cs[rnd.nextInt(3)]);
            }
            for (int i = 0; i < len2; i++) {
                b.append(cs[rnd.nextInt(3)]);
            }
            for (int i = 0; i < len3; i++) {
                c.append(cs[rnd.nextInt(3)]);
            }
            String sa = a.toString(), sb = b.toString(), sc = c.toString();
            int exp = brute(sa, sb, sc);
            int got = s.findMinimumOperations(sa, sb, sc);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " s1=" + sa + " s2=" + sb + " s3=" + sc
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, String s1, String s2, String s3, int exp) {
        int got = s.findMinimumOperations(s1, s2, s3);
        int exp2 = brute(s1, s2, s3);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " s1=" + s1
                + " s2=" + s2 + " s3=" + s3 + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(min(len))
  • 空间复杂度:O(1)

2938. Separate Black and White Balls(区分黑球与白球)

题目描述

有 n 个球,'1' 为黑、'0' 为白。每步可交换两个相邻球。返回将所有黑球移到右侧、

所有白球移到左侧所需的最小步数。

示例 1

复制代码
s = "101" -> 1

示例 2

复制代码
s = "100" -> 2

示例 3

复制代码
s = "0111" -> 0

提示:

  • 1 <= n == s.length <= 10^5

解题思路

解题思路:贪心统计

最终形态:所有 0 在前、所有 1 在后。等价的逆序数视角:需要的交换次数 =

每个 '1' 右侧 '0' 的个数之和(每个 1 必须跨过右侧所有 0)。

从右往左扫,遇 '1' 累加当前已见 '0' 个数;或从左往右遇 '0' 累加左侧 '1' 个数。

复杂度:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    public long minimumSteps(String s) {
        long ans = 0;
        long leftOnes = 0; // 已扫过左侧的 '1' 个数
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '1') {
                leftOnes++;
            } else {
                ans += leftOnes; // 该 0 需与左侧每个 1 交换
            }
        }
        return ans;
    }

    // ========== 本地 main 自测 ==========

    // 独立暴力:模拟冒泡(每次移动最右边的 1)
    private static long brute(String s) {
        char[] ch = s.toCharArray();
        int n = ch.length;
        long cnt = 0;
        // 把 1 移到右侧:从右到左处理每个 1
        int target = n - 1;
        for (int i = n - 1; i >= 0; i--) {
            if (ch[i] == '1') {
                cnt += target - i;
                target--;
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, "101", 1);
        check(s, "100", 2);
        check(s, "0111", 0);

        // 随机对拍
        Random rnd = new Random(59);
        char[] bits = {'0', '1'};
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(14);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append(bits[rnd.nextInt(2)]);
            }
            String str = sb.toString();
            long exp = brute(str);
            long got = s.minimumSteps(str);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " s=" + str + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, String str, long exp) {
        long got = s.minimumSteps(str);
        long exp2 = brute(str);
        System.out.println((got == exp && got == exp2 ? "PASS" : "FAIL") + " s=" + str
                + " got=" + got + " exp=" + exp + " brute=" + exp2);
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2939. Maximum Xor Product(最大异或乘积)

题目描述

给定三个整数 a、b、n,返回 (a XOR x) * (b XOR x) 的最大值,其中 0 <= x < 2^n。

答案对 1e9+7 取模。

示例 1

复制代码
a=12, b=5, n=4 -> 98(x=2 时 14*7)

示例 2

复制代码
a=6, b=7, n=5 -> 930(x=25 时 31*30)

示例 3

复制代码
a=1, b=6, n=3 -> 12(x=5 时 4*3)

提示:

  • 0 <= a, b < 2^50
  • 0 <= n <= 50

解题思路

解题思路:逐位贪心 + 高低均衡

x 只能影响低 n 位(>= n 的位 x 为 0,两数保持原样)。

从高位 i = n-1 到 0 逐位决策 x 的第 i 位:

  1. a、b 同一位都为 0:x_i 取 1 使两个数该位都从 0 变 1,乘积严格增大;
    都为 1:x_i 取 0 保持;
  2. a、b 该位不同:无论 x_i 取什么,(A,B) 该位总是一个 1 一个 0(和不改变),
    只需把 1 给"当前更小"的那个数(两数和相等时乘积随 |A-B| 减小而增大)。
    比较时只看已决策的高位(低位清零,因为低位后续也会同样趋于均衡,
    且高位差值大于任何低位组合,按高位比较决策是安全的)。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
import java.util.Random;

class Solution {
    private static final long MOD = 1_000_000_007;

    public int maximumXorProduct(long a, long bv, int n) {
        long x = 0;
        for (int i = n - 1; i >= 0; i--) {
            long ba = (a >> i) & 1;
            long bb = (bv >> i) & 1;
            if (ba == bb) {
                if (ba == 0) {
                    x |= 1L << i; // 两数该位都置 1
                }
                // ba == 1 时保持 x_i = 0
            } else {
                // 该位不同:比较已决策高位的 A、B 大小
                long hiA = (a ^ x) >> (i + 1);
                long hiB = (bv ^ x) >> (i + 1);
                if (hiA < hiB) {
                    // 让 A 得 1:需要 A_i = ba ^ x_i = 1 => x_i = 1 - ba = bb
                    if (bb == 1) {
                        x |= 1L << i;
                    }
                } else {
                    // 让 B 得 1:需要 B_i = bb ^ x_i = 1 => x_i = 1 - bb = ba
                    if (ba == 1) {
                        x |= 1L << i;
                    }
                }
            }
        }
        long A = (a ^ x) % MOD;
        long B = (bv ^ x) % MOD;
        return (int) (A * B % MOD);
    }

    // ========== 本地 main 自测 ==========

    // 暴力对照:小 n 枚举全部 x
    private static long brute(long a, long b, int n) {
        long best = -1;
        long limit = 1L << n;
        for (long x = 0; x < limit; x++) {
            long v = (a ^ x) * (b ^ x);
            if (v > best) {
                best = v;
            }
        }
        return best % MOD;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, 12, 5, 4, 98);
        check(s, 6, 7, 5, 930);
        check(s, 1, 6, 3, 12);
        check(s, 0, 0, 1, 1); // x=1 -> 1*1

        // 随机对拍
        Random rnd = new Random(61);
        for (int t = 0; t < 2000; t++) {
            long a = rnd.nextInt(1 << 10);
            long b = rnd.nextInt(1 << 10);
            int n = rnd.nextInt(11);
            long exp = brute(a, b, n);
            long got = s.maximumXorProduct(a, b, n);
            if (exp != got) {
                System.out.println("FAIL t=" + t + " a=" + a + " b=" + b + " n=" + n
                        + " got=" + got + " exp=" + exp);
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, long a, long b, int n, long exp) {
        long got = s.maximumXorProduct(a, b, n);
        System.out.println((got == exp ? "PASS" : "FAIL") + " a=" + a + " b=" + b
                + " n=" + n + " got=" + got + " exp=" + exp + " (brute=" + brute(a, b, n) + ")");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2940. Find Building Where Alice and Bob Can Meet(找到 Alice 和 Bob 可以相遇的建筑)

题目描述

给定正整数数组 heights。若 i < j 且 heights[i] < heights[j],则人可从 i 移动到 j。

查询 queries[i] = `ai`, `bi`,Alice 在 ai、Bob 在 bi,求两人能相遇的最左建筑下标;

无法相遇返回 -1。两人可各自移动(也允许静止),最终到达同一建筑 j。

示例 1

复制代码
heights = [6,4,8,5,2,7], queries = [[0,1],[0,3],[2,4],[3,4],[2,2]] -> [2,5,-1,5,2]

示例 2

复制代码
heights = [5,3,8,2,6,1,4,6], queries = [[0,7],[3,5],[5,2],[3,0],[1,6]] -> [7,6,-1,4,6]

提示:

  • 1 <= heights.length <= 5*10^4
  • 1 <= heights[i] <= 10^9
  • 1 <= queries.length <= 5*10^4

解题思路

解题思路:线段树二分 + 最右首个大于

对查询 (a, b),设 i = min(a,b),j = max(a,b)(谁大谁前)。

分情况:

  1. a == b:答案 a。
  2. heightsj > heightsi:Bob(或 Alice)可直接走到对方,答案 j。
  3. 否则需找 k > j 且 heightsk > max(heightsi, heightsj) 的最左 k:
    线段树维护区间最大值,在 j+1, n) 上二分找第一个值 \> 阈值 的位置, 即 k = queryFirstGreater(j+1, n-1, threshold);找不到返回 -1。 注意:答案要求"最左建筑"------满足两人都能到达的最左 k:两人中位置靠后者在 j, 任何 k \> j 且高度大于两人中较高者即可(i 侧的人也能到 k:heights\[i < heightsk 成立,
    因为 heightsk > 两人高度)。因此只需在 [j+1, n) 找第一个大于阈值的下标。
    复杂度:
  • 时间复杂度:O((n + q) log n)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

class Solution {
    public int[] leftmostBuildingQueries(int[] heights, int[][] queries) {
        int n = heights.length;
        int[] tree = new int[4 * n];
        build(tree, heights, 1, 0, n - 1);
        int[] ans = new int[queries.length];
        for (int qi = 0; qi < queries.length; qi++) {
            int a = queries[qi][0], b = queries[qi][1];
            if (a == b) {
                ans[qi] = a;
                continue;
            }
            int i = Math.min(a, b);
            int j = Math.max(a, b);
            if (heights[j] > heights[i]) {
                ans[qi] = j;
                continue;
            }
            int threshold = Math.max(heights[i], heights[j]);
            ans[qi] = firstGreater(tree, heights, 1, 0, n - 1, j + 1, n - 1, threshold);
        }
        return ans;
    }

    private void build(int[] tree, int[] h, int node, int l, int r) {
        if (l == r) {
            tree[node] = h[l];
            return;
        }
        int mid = (l + r) >>> 1;
        build(tree, h, node * 2, l, mid);
        build(tree, h, node * 2 + 1, mid + 1, r);
        tree[node] = Math.max(tree[node * 2], tree[node * 2 + 1]);
    }

    // 在 [ql, qr] 中找第一个值 > threshold 的下标;不存在返回 -1
    private int firstGreater(int[] tree, int[] h, int node, int l, int r, int ql, int qr, int threshold) {
        if (ql > qr || r < ql || l > qr || tree[node] <= threshold) {
            return -1;
        }
        if (l == r) {
            return l;
        }
        int mid = (l + r) >>> 1;
        int left = firstGreater(tree, h, node * 2, l, mid, ql, qr, threshold);
        if (left != -1) {
            return left;
        }
        return firstGreater(tree, h, node * 2 + 1, mid + 1, r, ql, qr, threshold);
    }

    // ========== 本地 main 自测 ==========

    // 暴力对照:对每个查询枚举所有候选 k
    private static int[] brute(int[] h, int[][] queries) {
        int n = h.length;
        int[] ans = new int[queries.length];
        for (int qi = 0; qi < queries.length; qi++) {
            int a = queries[qi][0], b = queries[qi][1];
            // 枚举可能相遇的建筑 k:a 到 k 可达(a==k 或 a<k 且 h[a]<h[k]),b 同理
            int best = Integer.MAX_VALUE;
            for (int k = 0; k < n; k++) {
                boolean canA = a == k || (a < k && h[a] < h[k]);
                boolean canB = b == k || (b < k && h[b] < h[k]);
                if (canA && canB) {
                    best = Math.min(best, k);
                }
            }
            ans[qi] = best == Integer.MAX_VALUE ? -1 : best;
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        // 官方案例
        check(s, new int[]{6, 4, 8, 5, 2, 7},
                new int[][]{{0, 1}, {0, 3}, {2, 4}, {3, 4}, {2, 2}}, new int[]{2, 5, -1, 5, 2});
        check(s, new int[]{5, 3, 8, 2, 6, 1, 4, 6},
                new int[][]{{0, 7}, {3, 5}, {5, 2}, {3, 0}, {1, 6}}, new int[]{7, 6, -1, 4, 6});

        // 随机对拍
        Random rnd = new Random(67);
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(9);
            int[] h = new int[n];
            for (int i = 0; i < n; i++) {
                h[i] = 1 + rnd.nextInt(20);
            }
            int q = 1 + rnd.nextInt(10);
            int[][] queries = new int[q][2];
            for (int i = 0; i < q; i++) {
                queries[i][0] = rnd.nextInt(n);
                queries[i][1] = rnd.nextInt(n);
            }
            int[] exp = brute(h, queries);
            int[] got = s.leftmostBuildingQueries(h, queries);
            if (!Arrays.equals(exp, got)) {
                System.out.println("FAIL t=" + t + " h=" + Arrays.toString(h)
                        + " q=" + Arrays.deepToString(queries) + " got=" + Arrays.toString(got)
                        + " exp=" + Arrays.toString(exp));
                return;
            }
        }
        System.out.println("ALL PASS");
    }

    private static void check(Solution s, int[] h, int[][] queries, int[] exp) {
        int[] got = s.leftmostBuildingQueries(h, queries);
        System.out.println((Arrays.equals(got, exp) ? "PASS" : "FAIL") + " h="
                + Arrays.toString(h) + " got=" + Arrays.toString(got) + " exp=" + Arrays.toString(exp));
    }
}

复杂度分析

  • 时间复杂度:O((n + q) log n)
  • 空间复杂度:O(n)
相关推荐
北漂人Java1 小时前
基于 Spring AI Alibaba WorkFlow 构建 Java 生产级 AI Agent
java·人工智能·spring
用户112936112192 小时前
编译全绿,上线就崩:MyBatis 五层方法名大小写埋下的坑
java
文丑颜不良啊2 小时前
LeetCode2901~2920题目解析
java·leetcode
码云社区2 小时前
能碳管理系统开发要关注什么?能源数据、统计口径与AI分析的衔接
java·人工智能·ai·能源·能源管理系统·能碳管理系统·能碳管理系统源码
Flynn Liang3 小时前
Agentic 工作流:子代理、Supervisor 与人工介入
java·langchain·多智能体·supervisor·agentic·langchain4j
xys_6783 小时前
JavaSE通关手册:运算符
java·java-ee
jyOverQ4 小时前
从零学 JVM(七):垃圾收集器详解——Serial、ParNew、Parallel、CMS 与 G1
java·jvm
liangbo74 小时前
Spring系列11:切点表达式
java·spring
3095566664 小时前
股票接口返回 404 与空数组的排查笔记:证书状态与级别参数
java·笔记·python·ios·iphone·股票数据api·股票数据接口