LeetCode 2921-2940. 题解合集
文章目录
- [LeetCode 2921-2940. 题解合集](#LeetCode 2921-2940. 题解合集)
-
- [2921. Maximum Profitable Triplets With Increasing Prices II(价格递增的最大利润三元组 II)](#2921. Maximum Profitable Triplets With Increasing Prices II(价格递增的最大利润三元组 II))
- [2922. Market Analysis III(市场分析 III)](#2922. Market Analysis III(市场分析 III))
- [2923. Find Champion I(找到冠军 I)](#2923. Find Champion I(找到冠军 I))
- [2924. Find Champion II(找到冠军 II)](#2924. Find Champion II(找到冠军 II))
- [2925. Maximum Score After Applying Operations on a Tree(在树上执行操作以后得到的最大分数)](#2925. Maximum Score After Applying Operations on a Tree(在树上执行操作以后得到的最大分数))
- [2926. Maximum Balanced Subsequence Sum(平衡子序列的最大和)](#2926. Maximum Balanced Subsequence Sum(平衡子序列的最大和))
- [2927. Distribute Candies Among Children III(给小朋友们分糖果 III)](#2927. Distribute Candies Among Children III(给小朋友们分糖果 III))
- [2928. Distribute Candies Among Children I(给小朋友们分糖果 I)](#2928. Distribute Candies Among Children I(给小朋友们分糖果 I))
- [2929. Distribute Candies Among Children II(给小朋友们分糖果 II)](#2929. Distribute Candies Among Children II(给小朋友们分糖果 II))
- [2930. Number of Strings Which Can Be Rearranged to Contain Substring(重新排列后包含指定子字符串的字符串数目)](#2930. Number of Strings Which Can Be Rearranged to Contain Substring(重新排列后包含指定子字符串的字符串数目))
- [2931. Maximum Spending After Buying Items(购买物品的最大开销)](#2931. Maximum Spending After Buying Items(购买物品的最大开销))
- [2932. Maximum Strong Pair XOR I(找出强数对的最大异或值 I)](#2932. Maximum Strong Pair XOR I(找出强数对的最大异或值 I))
- [2933. High-Access Employees(高访问员工)](#2933. High-Access Employees(高访问员工))
- [2934. Minimum Operations to Maximize Last Elements in Arrays(最大化数组末位元素的最少操作次数)](#2934. Minimum Operations to Maximize Last Elements in Arrays(最大化数组末位元素的最少操作次数))
- [2935. Maximum Strong Pair XOR II(找出强数对的最大异或值 II)](#2935. Maximum Strong Pair XOR II(找出强数对的最大异或值 II))
- [2936. Number of Equal Numbers Blocks(包含相等值数字块的数量)](#2936. Number of Equal Numbers Blocks(包含相等值数字块的数量))
- [2937. Make Three Strings Equal(使三个字符串相等)](#2937. Make Three Strings Equal(使三个字符串相等))
- [2938. Separate Black and White Balls(区分黑球与白球)](#2938. Separate Black and White Balls(区分黑球与白球))
- [2939. Maximum Xor Product(最大异或乘积)](#2939. Maximum Xor Product(最大异或乘积))
- [2940. Find Building Where Alice and Bob Can Meet(找到 Alice 和 Bob 可以相遇的建筑)](#2940. Find Building Where Alice and Bob Can Meet(找到 Alice 和 Bob 可以相遇的建筑))
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 <= 500001 <= prices[i] <= 50001 <= 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 <= 100grid[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^41 <= 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^81 <= 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 <= 501 <= 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^61 <= 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 <= 10001 <= nums1[i], nums2[i] <= 10^9
解题思路
解题思路:枚举末两位是否交换
末位 n-1 处是否交换只影响其自身值。分两类情况:
- 不交换末位:a = nums1n-1, b = nums2n-1 保持不变;
- 交换末位: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^41 <= 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^151 <= 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^500 <= n <= 50
解题思路
解题思路:逐位贪心 + 高低均衡
x 只能影响低 n 位(>= n 的位 x 为 0,两数保持原样)。
从高位 i = n-1 到 0 逐位决策 x 的第 i 位:
- a、b 同一位都为 0:x_i 取 1 使两个数该位都从 0 变 1,乘积严格增大;
都为 1:x_i 取 0 保持; - 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^41 <= heights[i] <= 10^91 <= queries.length <= 5*10^4
解题思路
解题思路:线段树二分 + 最右首个大于
对查询 (a, b),设 i = min(a,b),j = max(a,b)(谁大谁前)。
分情况:
- a == b:答案 a。
- heightsj > heightsi:Bob(或 Alice)可直接走到对方,答案 j。
- 否则需找 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)