LeetCode 2521-2540. 题解合集
文章目录
- [LeetCode 2521-2540. 题解合集](#LeetCode 2521-2540. 题解合集)
-
- [2521. Distinct Prime Factors of Product of Array(数组乘积中的不同质因数数目)](#2521. Distinct Prime Factors of Product of Array(数组乘积中的不同质因数数目))
- [2522. Partition String Into Substrings With Values at Most K](#2522. Partition String Into Substrings With Values at Most K)
- [2523. Closest Prime Numbers in Range(范围内最接近的两个质数)](#2523. Closest Prime Numbers in Range(范围内最接近的两个质数))
- [2524. Maximum Frequency Score of a Subarray(子数组的最大频率分数)](#2524. Maximum Frequency Score of a Subarray(子数组的最大频率分数))
- [2525. Categorize Box According to Criteria(根据规则将箱子分类)](#2525. Categorize Box According to Criteria(根据规则将箱子分类))
- [2526. Find Consecutive Integers from a Data Stream(找到数据流中的连续整数)](#2526. Find Consecutive Integers from a Data Stream(找到数据流中的连续整数))
- [2527. Find Xor-Beauty of Array(查询数组异或美丽值)](#2527. Find Xor-Beauty of Array(查询数组异或美丽值))
- [2528. Maximize the Minimum Powered City(最大化城市的最小电量)](#2528. Maximize the Minimum Powered City(最大化城市的最小电量))
- [2529. Maximum Count of Positive Integer and Negative Integer(正整数和负整数的最大计数)](#2529. Maximum Count of Positive Integer and Negative Integer(正整数和负整数的最大计数))
- [2530. Maximal Score After Applying K Operations(执行 K 次操作后的最大分数)](#2530. Maximal Score After Applying K Operations(执行 K 次操作后的最大分数))
- [2531. Make Number of Distinct Characters Equal(使字符串中不同字符的数目相等)](#2531. Make Number of Distinct Characters Equal(使字符串中不同字符的数目相等))
- [2532. Time to Cross a Bridge(过桥的时间)](#2532. Time to Cross a Bridge(过桥的时间))
- [2533. Number of Good Binary Strings(好二进制字符串的数量)](#2533. Number of Good Binary Strings(好二进制字符串的数量))
- [2534. Time Taken to Cross the Door(通过门的时间)](#2534. Time Taken to Cross the Door(通过门的时间))
- [2535. Difference Between Element Sum and Digit Sum of an Array(数组元素和与数字和的绝对差)](#2535. Difference Between Element Sum and Digit Sum of an Array(数组元素和与数字和的绝对差))
- [2536. Increment Submatrices by One(子矩阵元素加 1)](#2536. Increment Submatrices by One(子矩阵元素加 1))
- [2537. Count the Number of Good Subarrays(统计好子数组的数目)](#2537. Count the Number of Good Subarrays(统计好子数组的数目))
- [2538. Difference Between Maximum and Minimum Price Sum(最大价值和与最小价值和的差值)](#2538. Difference Between Maximum and Minimum Price Sum(最大价值和与最小价值和的差值))
- [2539. Count the Number of Good Subsequences(好子序列的个数)](#2539. Count the Number of Good Subsequences(好子序列的个数))
- [2540. Minimum Common Value(最小公共值)](#2540. Minimum Common Value(最小公共值))
2521. Distinct Prime Factors of Product of Array(数组乘积中的不同质因数数目)
题目描述
给你一个正整数数组 nums,对 nums 所有元素求积之后,找出并返回乘积中不同质因数的数目。
质数是指大于 1 且仅能被 1 及自身整除的数字。如果 val2 / val1 是一个整数,则整数 val1 是
另一个整数 val2 的一个因数。
示例 1
`nums = [2,4,3,7,10,6]` → 4(10080 = 2^5 × 3^2 × 5 × 7)
示例 2
`nums = [2,4,8,16]` → 1(1024 = 2^10)
提示:
1 <= nums.length <= 10^42 <= nums[i] <= 1000
解题思路
解法一:逐个元素试除分解(主提交版本)
- 对每个 numsi 用试除法分解:因子 d 从 2 到 sqrt(x),若能整除则加入集合并把 x 除以 d 直到
除不尽,最后 x > 1 时加入集合。返回集合大小。 - 复杂度 O(n × sqrt(max)),max ≤ 1000 时极快。
解法二:SPF 最小质因子分解(对照) - 预筛 1...1000 的每个数的最小质因子 spf,分解每个元素时用 spf 快速取出质因子加入集合。
复杂度: - 时间复杂度:O(n * sqrt(1000))
- 空间复杂度:O(不同质因子数)
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
// 解法一:逐个元素试除分解(主提交版本)
public int distinctPrimeFactors(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) {
int v = x;
for (int d = 2; d * d <= v; d++) {
if (v % d == 0) {
set.add(d);
while (v % d == 0) {
v /= d;
}
}
}
if (v > 1) {
set.add(v);
}
}
return set.size();
}
// 解法二:SPF 最小质因子分解(对照)
public int distinctPrimeFactorsSpf(int[] nums) {
int maxN = 1000;
int[] spf = new int[maxN + 1];
java.util.List<Integer> primes = new java.util.ArrayList<>();
for (int i = 2; i <= maxN; i++) {
if (spf[i] == 0) {
spf[i] = i;
primes.add(i);
}
for (int p : primes) {
if (p > spf[i] || i * p > maxN) {
break;
}
spf[i * p] = p;
}
}
Set<Integer> set = new HashSet<>();
for (int x : nums) {
int v = x;
while (v > 1) {
set.add(spf[v]);
v /= spf[v];
}
}
return set.size();
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.distinctPrimeFactors(new int[]{2, 4, 3, 7, 10, 6}) == 4 : "2521 例1 主";
assert sol.distinctPrimeFactorsSpf(new int[]{2, 4, 3, 7, 10, 6}) == 4 : "2521 例1 对照";
assert sol.distinctPrimeFactors(new int[]{2, 4, 8, 16}) == 1 : "2521 例2 主";
assert sol.distinctPrimeFactorsSpf(new int[]{2, 4, 8, 16}) == 1 : "2521 例2 对照";
assert sol.distinctPrimeFactors(new int[]{1000}) == 2 : "2521 附加 1000=2^3*5^3";
assert sol.distinctPrimeFactors(new int[]{999}) == 3 : "2521 附加 999=3^3*37";
// 随机对拍
java.util.Random rnd = new java.util.Random(2121);
for (int t = 0; t < 1000; t++) {
int n = 1 + rnd.nextInt(30);
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = 2 + rnd.nextInt(999);
}
int a = sol.distinctPrimeFactors(arr);
int b = sol.distinctPrimeFactorsSpf(arr);
if (a != b) {
System.out.println("对拍失败 " + java.util.Arrays.toString(arr) + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2521 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n * sqrt(1000))
- 空间复杂度:O(不同质因子数)
2522. Partition String Into Substrings With Values at Most K
题目描述
(将字符串分割成值不超过 K 的子字符串)
给你一个字符串 s,它每一位都是 '1' 到 '9' 之间的数字组成,同时给你一个整数 k。
如果一个字符串 s 的分割满足以下条件,我们称它是一个好分割:
s中每个数位恰好属于一个子字符串;- 每个子字符串的值都小于等于
k。
请你返回s所有的好分割中,子字符串的最少数目。如果不存在s的好分割,返回 -1。
一个字符串的值是这个字符串对应的整数;子字符串是字符串中一段连续的字符序列。
示例 1
`s = "165462", k = 60` → 4("16","54","6","2")
示例 2
`s = "238182", k = 5` → -1
提示:
1 <= s.length <= 10^5s[i]是 '1' 到 '9' 之间的数字1 <= k <= 10^9
解题思路
解法一:贪心------每次取尽可能长的合法段(主提交版本)
- 从前往后扫描,维护当前段的值 val(long 防溢出)。对每个字符:
若 newVal = oldVal * 10 + digit <= k 则并入当前段;否则当前段结束、以该字符开新段并段数 +1。 - 若单个数字字符 digit > k,则任何分割都无法容纳该字符,直接返回 -1。
- 正确性:段越长则段数越少,贪心取最长合法段全局最优,任何提前切段只会增加段数。
- 复杂度 O(n)。
解法二:动态规划(对照) - dpi = 分割 s0...i-1 的最少段数,dp0 = 0。
- 对每个 i 枚举最后一段的起点 j(j 从 i-1 往前),维护子串值 val(long),
若 val <= k 则 dpi = min(dpi, dpj + 1);val 一旦超过 k 就 break(往前只会更大)。 - 仅用于小数据对拍。
复杂度: - 时间复杂度:O(n)
- 空间复杂度:O(1)
代码实现
java
import java.util.Arrays;
public class Solution {
// 解法一:贪心取最长段(主提交版本)
public int minimumPartition(String s, int k) {
int n = s.length();
int cnt = 0; // 已完成切割的段数
long cur = 0; // 当前段值
for (int i = 0; i < n; i++) {
int d = s.charAt(i) - '0';
if (d > k) {
return -1;
}
long nxt = cur * 10 + d;
if (nxt <= k) {
cur = nxt;
} else {
cnt++;
cur = d;
}
}
return cnt + 1; // 最后一段未计入 cnt
}
// 解法二:动态规划(对照,小数据)
public int minimumPartitionDp(String s, int k) {
int n = s.length();
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE / 2);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
long val = 0;
long p = 1;
for (int j = i - 1; j >= 0; j--) {
val = (long) (s.charAt(j) - '0') * p + val;
p *= 10;
if (val > k) {
break; // 再往前拼接只会更大
}
dp[i] = Math.min(dp[i], dp[j] + 1);
}
}
return dp[n] >= Integer.MAX_VALUE / 2 ? -1 : dp[n];
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.minimumPartition("165462", 60) == 4 : "2522 例1 主";
assert sol.minimumPartitionDp("165462", 60) == 4 : "2522 例1 对照";
assert sol.minimumPartition("238182", 5) == -1 : "2522 例2 主";
assert sol.minimumPartitionDp("238182", 5) == -1 : "2522 例2 对照";
assert sol.minimumPartition("1234", 10) == 3 : "2522 附加 1|2|34";
assert sol.minimumPartition("9", 9) == 1 : "2522 附加 单字符";
// 随机对拍
java.util.Random rnd = new java.util.Random(2222);
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(10);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(1 + rnd.nextInt(9));
}
int k = 1 + rnd.nextInt(1000);
int a = sol.minimumPartition(sb.toString(), k);
int b = sol.minimumPartitionDp(sb.toString(), k);
if (a != b) {
System.out.println("对拍失败 s=" + sb + " k=" + k + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2522 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
2523. Closest Prime Numbers in Range(范围内最接近的两个质数)
题目描述
给你两个正整数 left 和 right,请你找到两个整数 num1 和 num2,它们满足:
left<= num1 < num2 <=right;num1和num2都是质数;num2-num1是满足上述条件的质数对中的最小值。
请你返回正整数数组ans= `num1`, `num2`。如果有多个整数对满足上述条件,请你返回num1最小的质数对;
如果不存在符合题意的质数对,返回 -1, -1。
示例 1
`left = 10, right = 19` → [11,13]
示例 2
`left = 4, right = 6` → [-1,-1]
提示:
1 <= left <= right <= 10^6
解题思路
解法一:埃氏筛 + 相邻质数扫描(主提交版本)
- 用埃氏筛预处理 1, right 内所有数是否为质数(布尔数组)。
- 遍历 left, right 中所有质数,记录相邻质数对的最小差值;若差值相等取 num1 更小的。
- 复杂度 O(right log log right)。
解法二:试除法逐个判定(对照) - 对区间内每个数用 sqrt 试除判定质数,收集质数列表后扫描相邻对。
- 复杂度 O(√right × 区间长度),计算量大但作为小范围对照可行。
复杂度: - 时间复杂度:O(right log log right)
- 空间复杂度:O(right)
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// 解法一:埃氏筛(主提交版本)
public int[] closestPrimes(int left, int right) {
boolean[] isPrime = new boolean[right + 1];
Arrays.fill(isPrime, 2, right + 1, true);
for (int i = 2; (long) i * i <= right; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= right; j += i) {
isPrime[j] = false;
}
}
}
int bestDiff = Integer.MAX_VALUE;
int n1 = -1, n2 = -1;
int prev = -1;
for (int x = left; x <= right; x++) {
if (!isPrime[x]) {
continue;
}
if (prev != -1) {
int diff = x - prev;
if (diff < bestDiff) {
bestDiff = diff;
n1 = prev;
n2 = x;
}
// 差值相等时保留 num1 更小的,即不更新(prev 已经是较小的)
}
prev = x;
}
return new int[]{n1, n2};
}
// 解法二:试除法判定(对照)
public int[] closestPrimesTrial(int left, int right) {
List<Integer> primes = new ArrayList<>();
for (int x = Math.max(2, left); x <= right; x++) {
if (isPrime(x)) {
primes.add(x);
}
}
if (primes.size() < 2) {
return new int[]{-1, -1};
}
int bestDiff = Integer.MAX_VALUE;
int n1 = -1, n2 = -1;
for (int i = 0; i + 1 < primes.size(); i++) {
int diff = primes.get(i + 1) - primes.get(i);
if (diff < bestDiff) {
bestDiff = diff;
n1 = primes.get(i);
n2 = primes.get(i + 1);
}
}
return new int[]{n1, n2};
}
private boolean isPrime(int x) {
if (x < 2) {
return false;
}
for (int d = 2; (long) d * d <= x; d++) {
if (x % d == 0) {
return false;
}
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
assert Arrays.equals(sol.closestPrimes(10, 19), new int[]{11, 13}) : "2523 例1 主";
assert Arrays.equals(sol.closestPrimesTrial(10, 19), new int[]{11, 13}) : "2523 例1 对照";
assert Arrays.equals(sol.closestPrimes(4, 6), new int[]{-1, -1}) : "2523 例2 主";
assert Arrays.equals(sol.closestPrimesTrial(4, 6), new int[]{-1, -1}) : "2523 例2 对照";
assert Arrays.equals(sol.closestPrimes(1, 1), new int[]{-1, -1}) : "2523 附加 无质数";
assert Arrays.equals(sol.closestPrimes(2, 3), new int[]{2, 3}) : "2523 附加 最小对";
assert Arrays.equals(sol.closestPrimes(19, 31), new int[]{29, 31}) : "2523 附加 19-31";
// 随机对拍
java.util.Random rnd = new java.util.Random(2323);
for (int t = 0; t < 1000; t++) {
int a = 1 + rnd.nextInt(5000);
int b = a + rnd.nextInt(2000);
int[] r1 = sol.closestPrimes(a, b);
int[] r2 = sol.closestPrimesTrial(a, b);
if (!Arrays.equals(r1, r2)) {
System.out.println("对拍失败 left=" + a + " right=" + b + " 主=" + Arrays.toString(r1) + " 对照=" + Arrays.toString(r2));
System.exit(1);
}
}
System.out.println("2523 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(right log log right)
- 空间复杂度:O(right)
2524. Maximum Frequency Score of a Subarray(子数组的最大频率分数)
题目描述
给定一个整数数组 nums 和一个正整数 k。
数组的频率得分是数组中不同值的幂次之和,并将和对 10^9 + 7 取模。
例如,数组 5,4,5,7,4,4 的频率得分为 (4^3 + 5^2 + 7^1) mod (10^9+7) = 96。
返回 nums 中长度为 k 的子数组的最大频率得分(取模后的最大值)。
子数组是一个数组的连续部分。
示例 1
`nums = [1,1,1,2,1,2], k = 3` → 5(子数组 [2,1,2]:2^2 + 1^1 = 5)
示例 2
`nums = [1,1,1,1,1,1], k = 4` → 1
提示:
1 <= k <= nums.length <= 10^51 <= nums[i] <= 10^6
解题思路
解法一:滑动窗口 + 幂表维护(主提交版本)
- 对固定长度 k 的窗口维护频率表 freq 与当前分数 score = Σ pow(v, freqv)。
- 窗口右移一格:
a) 移出旧值 v:先减 pow(v, freqv),频率减 1;若新频率 > 0 再加回 pow(v, freqv-1);
b) 加入新值 v:先减 pow(v, freqv)(若已存在),频率加 1,再加 pow(v, freqv+1)。 - pow 用快速幂 O(log MOD);总复杂度 O(n log MOD)。
解法二:暴力重算每个窗口(对照,小数据) - 对每个窗口统计频率后重算分数,与主解法交叉验证。
复杂度: - 时间复杂度:O(n log MOD)
- 空间复杂度:O(k)
代码实现
java
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Solution {
private static final int MOD = 1_000_000_007;
// 解法一:滑动窗口维护频率分数(主提交版本)
public int maxFrequencyScore(int[] nums, int k) {
int n = nums.length;
Map<Integer, Integer> freq = new HashMap<>();
long score = 0;
// 初始窗口
for (int i = 0; i < k; i++) {
int v = nums[i];
int oldf = freq.getOrDefault(v, 0);
if (oldf > 0) {
score = (score - pow(v, oldf) + MOD) % MOD;
}
freq.put(v, oldf + 1);
score = (score + pow(v, oldf + 1)) % MOD;
}
long best = score;
for (int i = k; i < n; i++) {
// 移出 nums[i-k]
int out = nums[i - k];
int fo = freq.get(out);
score = (score - pow(out, fo) + MOD) % MOD;
if (fo == 1) {
freq.remove(out);
} else {
freq.put(out, fo - 1);
score = (score + pow(out, fo - 1)) % MOD;
}
// 加入 nums[i]
int in = nums[i];
int oldf = freq.getOrDefault(in, 0);
if (oldf > 0) {
score = (score - pow(in, oldf) + MOD) % MOD;
}
freq.put(in, oldf + 1);
score = (score + pow(in, oldf + 1)) % MOD;
best = Math.max(best, score);
}
return (int) best;
}
private long pow(long a, long b) {
a %= MOD;
long res = 1;
while (b > 0) {
if ((b & 1) == 1) {
res = res * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return res;
}
// 解法二:暴力重算每个窗口(对照,小数据)
public int maxFrequencyScoreBrute(int[] nums, int k) {
int n = nums.length;
long best = 0;
for (int i = 0; i + k <= n; i++) {
Map<Integer, Integer> f = new HashMap<>();
for (int j = i; j < i + k; j++) {
f.merge(nums[j], 1, Integer::sum);
}
long s = 0;
for (Map.Entry<Integer, Integer> e : f.entrySet()) {
s = (s + pow(e.getKey(), e.getValue())) % MOD;
}
best = Math.max(best, s);
}
return (int) best;
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.maxFrequencyScore(new int[]{1, 1, 1, 2, 1, 2}, 3) == 5 : "2524 例1 主";
assert sol.maxFrequencyScoreBrute(new int[]{1, 1, 1, 2, 1, 2}, 3) == 5 : "2524 例1 对照";
assert sol.maxFrequencyScore(new int[]{1, 1, 1, 1, 1, 1}, 4) == 1 : "2524 例2 主";
assert sol.maxFrequencyScoreBrute(new int[]{1, 1, 1, 1, 1, 1}, 4) == 1 : "2524 例2 对照";
assert sol.maxFrequencyScore(new int[]{2, 2, 2}, 2) == 4 : "2524 附加 幂次";
assert sol.maxFrequencyScore(new int[]{1, 2, 3}, 1) == 3 : "2524 附加 k=1";
// 随机对拍
Random rnd = new Random(2424);
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(15);
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = 1 + rnd.nextInt(6);
}
int k = 1 + rnd.nextInt(n);
int a = sol.maxFrequencyScore(arr, k);
int b = sol.maxFrequencyScoreBrute(arr, k);
if (a != b) {
System.out.println("对拍失败 " + Arrays.toString(arr) + " k=" + k + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2524 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n log MOD)
- 空间复杂度:O(k)
2525. Categorize Box According to Criteria(根据规则将箱子分类)
题目描述
给你四个整数 length、width、height 和 mass,分别表示一个箱子的三个维度和质量,
返回一个表示箱子类别的字符串:
- 如果满足以下条件,那么箱子是 "
Bulky" 的:
箱子至少有一个维度大于等于 10^4,或者箱子的体积大于等于 10^9。 - 如果箱子的质量大于等于 100,那么箱子是 "
Heavy" 的。 - 同时是 "
Bulky" 和 "Heavy" 返回 "Both";都不是返回 "Neither";
是 "Bulky" 但不是 "Heavy" 返回 "Bulky";是 "Heavy" 但不是 "Bulky" 返回 "Heavy"。
注意:箱子的体积等于长、宽、高的乘积。
示例 1
`length = 1000, width = 35, height = 700, mass = 300` → "Heavy"
示例 2
`length = 200, width = 50, height = 800, mass = 50` → "Neither"
提示:
1 <= length, width, height <= 10^51 <= mass <= 10^3
解题思路
解法一:条件判断(主提交版本)
- bulky = max(len,wid,hei) >= 10^4 或 (long)lenwidhei >= 10^9(用 long 防溢出)。
- heavy = mass >= 100。
- 按分类规则拼接返回。
解法二:分支枚举(对照) - 用 if-else 按四种情形显式判断,与解法一逻辑等价但写法不同,用于交叉验证。
复杂度: - 时间复杂度:O(1)
- 空间复杂度:O(1)
代码实现
java
import java.util.Random;
public class Solution {
// 解法一:布尔组合(主提交版本)
public String categorizeBox(int length, int width, int height, int mass) {
boolean bulky = length >= 10000 || width >= 10000 || height >= 10000
|| (long) length * width * height >= 1000000000L;
boolean heavy = mass >= 100;
if (bulky && heavy) {
return "Both";
}
if (!bulky && !heavy) {
return "Neither";
}
if (bulky) {
return "Bulky";
}
return "Heavy";
}
// 解法二:显式分支(对照)
public String categorizeBoxBranch(int length, int width, int height, int mass) {
long volume = (long) length * width * height;
boolean bulky = (length >= 10000 || width >= 10000 || height >= 10000)
|| volume >= 1000000000L;
boolean heavy = mass >= 100;
if (bulky && heavy) {
return "Both";
} else if (!bulky && !heavy) {
return "Neither";
} else if (bulky) {
return "Bulky";
} else {
return "Heavy";
}
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.categorizeBox(1000, 35, 700, 300).equals("Heavy") : "2525 例1 主";
assert sol.categorizeBoxBranch(1000, 35, 700, 300).equals("Heavy") : "2525 例1 对照";
assert sol.categorizeBox(200, 50, 800, 50).equals("Neither") : "2525 例2 主";
assert sol.categorizeBoxBranch(200, 50, 800, 50).equals("Neither") : "2525 例2 对照";
assert sol.categorizeBox(10000, 1, 1, 1).equals("Bulky") : "2525 附加 维度";
assert sol.categorizeBox(1000, 1000, 1000, 1).equals("Bulky") : "2525 附加 体积";
assert sol.categorizeBox(1000, 1000, 1000, 100).equals("Both") : "2525 附加 Both";
// 随机对拍
Random rnd = new Random(2525);
for (int t = 0; t < 300; t++) {
int l = 1 + rnd.nextInt(15000);
int w = 1 + rnd.nextInt(15000);
int h = 1 + rnd.nextInt(15000);
int m = 1 + rnd.nextInt(150);
String a = sol.categorizeBox(l, w, h, m);
String b = sol.categorizeBoxBranch(l, w, h, m);
if (!a.equals(b)) {
System.out.println("对拍失败 " + l + "," + w + "," + h + "," + m + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2525 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(1)
- 空间复杂度:O(1)
2526. Find Consecutive Integers from a Data Stream(找到数据流中的连续整数)
题目描述
给你一个整数数据流,请你实现一个数据结构,检查数据流中最后 k 个整数是否等于给定值 value。
实现 DataStream 类:
- DataStream(
intvalue,intk):用value和k初始化一个空的整数数据流。 booleanconsec(intnum):将num添加到整数数据流。如果后k个整数都等于value,返回true,
否则返回false。如果少于k个整数,条件不满足,也返回false。
示例
["DataStream","consec","consec","consec","consec"]
[[4,3],[4],[4],[4],[3]]
→ [null,false,false,true,false]
提示:
1 <= value, num <= 10^91 <= k <= 10^5- 至多调用
consec10^5 次
解题思路
解法一:仅维护「末尾连续等于 value 的个数」(主提交版本)
- 事实上并不需要记住整个窗口:只要记录从末尾往前连续等于 value 的个数 cnt。
对每个新数 num:若 num == value 则 cnt++,否则 cnt = 0;
返回 cnt >= k。 - 复杂度 O(1) 每次。
解法二:队列显式模拟(对照) - 用容量 k 的队列存最近 k 个整数,同时统计队列内等于 value 的数量 eq;
入队时若队列满则出队并更新 eq,再入队新数更新 eq,返回 eq == k。
(队列满时 eqk 且队列长度k 等价于最后 k 个都等于 value)
复杂度: - 时间复杂度:O(1) 每操作
- 空间复杂度:O(1)(解法一)或 O(k)(解法二)
代码实现
java
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Random;
public class Solution {
private final int value;
private final int k;
private int cnt;
public Solution() {
this.value = 0;
this.k = 0;
}
// 解法一:连续相等计数
public Solution(int value, int k) {
this.value = value;
this.k = k;
this.cnt = 0;
}
public boolean consec(int num) {
if (num == value) {
cnt++;
} else {
cnt = 0;
}
return cnt >= k;
}
// 解法二:队列模拟(照应类)
public static class DataStreamQueue {
private final int value;
private final int k;
private final Queue<Integer> q;
private int eq;
public DataStreamQueue(int value, int k) {
this.value = value;
this.k = k;
this.q = new ArrayDeque<>();
this.eq = 0;
}
public boolean consec(int num) {
if (q.size() == k) {
int out = q.poll();
if (out == value) {
eq--;
}
}
q.offer(num);
if (num == value) {
eq++;
}
return eq == k;
}
}
public static void main(String[] args) {
// 官方示例
Solution ds = new Solution(4, 3);
assert !ds.consec(4);
assert !ds.consec(4);
assert ds.consec(4);
assert !ds.consec(3);
DataStreamQueue dsq = new DataStreamQueue(4, 3);
assert !dsq.consec(4);
assert !dsq.consec(4);
assert dsq.consec(4);
assert !dsq.consec(3);
// 随机对拍
Random rnd = new Random(2626);
for (int t = 0; t < 300; t++) {
int value = 1 + rnd.nextInt(5);
int k = 1 + rnd.nextInt(6);
Solution a = new Solution(value, k);
DataStreamQueue b = new DataStreamQueue(value, k);
int steps = 1 + rnd.nextInt(50);
for (int s = 0; s < steps; s++) {
int num = 1 + rnd.nextInt(6);
boolean ra = a.consec(num);
boolean rb = b.consec(num);
if (ra != rb) {
System.out.println("对拍失败 value=" + value + " k=" + k + " step=" + s + " num=" + num);
System.exit(1);
}
}
}
System.out.println("2526 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(1) 每操作
- 空间复杂度:O(1)(解法一)或 O(k)(解法二)
2527. Find Xor-Beauty of Array(查询数组异或美丽值)
题目描述
给你一个下标从 0 开始的整数数组 nums。
三个下标 i、j 和 k 的有效值定义为 ((nums[i] | nums[j]) & nums[k])。
一个数组的异或美丽值是数组中所有满足 0 <= i, j, k < n 的三元组 (i, j, k) 的有效值的异或结果。
请你返回 nums 的异或美丽值。
示例 1
`nums = [1,4]` → 5(8 个三元组异或后为 5)
示例 2
`nums = [15,45,20,2,34,35,5,44,32,30]` → 34
提示:
1 <= nums.length <= 10^51 <= nums[i] <= 10^9
解题思路
解法一:按位分析化简(主提交版本)
- 目标是对所有 (i,j,k) 计算 v = (x|y)&z 并全部异或。按位独立:考虑第 b 位。
- 记 c = 原数组中第 b 位为 1 的元素个数,z0 = c,z1 = n - c。
对固定 (i,j,k):若 k 的位是 0,则 v 的位恒为 0;若 k 的位是 1,则 v 的位 = (x|y) 的位,
即 i、j 中至少一个的该位为 1。 - 第 b 位所有三元组异或结果为 1 ⟺ 贡献为 1 的三元组个数为奇数。
贡献 1 的三元组数 = (k 取位 1 的个数) × (i,j 至少一个位 1 的组合数)
= z1 × (n^2 - z0^2)。 - 该项为奇数时答案第 b 位为 1。由于 z1、n^2 - z0^2 的具体奇偶可以直接按位与 1 判断。
- 复杂度 O(n × 31)。
解法二:三重循环暴力(对照,小数据) - 对 n ≤ 60 的小数组直接三重循环枚举三元组求异或。
复杂度: - 时间复杂度:O(n * 31)
- 空间复杂度:O(1)
代码实现
java
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class Solution {
// 解法一:按位计数化简(主提交版本)
public int xorBeauty(int[] nums) {
int n = nums.length;
int ans = 0;
for (int b = 0; b < 31; b++) {
long c = 0; // 该位为 1 的个数
for (int x : nums) {
if ((x >> b & 1) == 1) {
c++;
}
}
// k 取到该位为 1 的数:c 种;i,j 至少一个该位为 1:n^2 - (n-c)^2
long cnt = c * (n * n - (n - c) * (n - c));
if ((cnt & 1) == 1) {
ans |= 1 << b;
}
}
return ans;
}
// 解法二:暴力三重循环(对照,小数据)
public int xorBeautyBrute(int[] nums) {
int n = nums.length;
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
ans ^= ((nums[i] | nums[j]) & nums[k]);
}
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.xorBeauty(new int[]{1, 4}) == 5 : "2527 例1 主";
assert sol.xorBeautyBrute(new int[]{1, 4}) == 5 : "2527 例1 对照";
assert sol.xorBeauty(new int[]{15, 45, 20, 2, 34, 35, 5, 44, 32, 30}) == 34 : "2527 例2 主";
assert sol.xorBeauty(new int[]{7}) == 7 : "2527 附加 单元素";
// 随机对拍(小数据)
Random rnd = new Random(2727);
for (int t = 0; t < 200; t++) {
int n = 1 + rnd.nextInt(7);
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = 1 + rnd.nextInt(30);
}
int a = sol.xorBeauty(arr);
int b = sol.xorBeautyBrute(arr);
if (a != b) {
System.out.println("对拍失败 " + java.util.Arrays.toString(arr) + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2527 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n * 31)
- 空间复杂度:O(1)
2528. Maximize the Minimum Powered City(最大化城市的最小电量)
题目描述
给你一个下标从 0 开始长度为 n 的整数数组 stations,其中 stations[i] 表示第 i 座城市的供电站数目。
每个供电站可以在一定范围内给所有城市提供电力:如果范围是 r,在城市 i 处的供电站可以给所有满足
|i-j| <= r 且 0 <= i, j <= n-1 的城市 j 供电。一座城市的电量是所有能给它供电的供电站数目。
政府批准了可以额外建造 k 座供电站,需要决定建在哪里,这些供电站与已有的供电站有相同的供电范围 r。
给定 r 和 k,如果以最优策略建造额外的发电站,返回所有城市中最小电量的最大值。
示例 1
`stations = [1,2,4,5,0], r = 1, k = 2` → 5
示例 2
`stations = [4,4,4,4], r = 0, k = 3` → 4
提示:
1 <= n <= 10^50 <= stations[i] <= 10^50 <= r <= n-10 <= k <= 10^9
解题思路
解法一:二分答案 + 前缀和滑动窗口 + 差分贪心(主提交版本)
- 先求每座城市的初始电量 poweri = Σ stationsj(|j-i| <= r),用滑动窗口 O(n)。
- 二分目标 x:检查能否用不超过 k 座新站让每座城市电量 >= x。
- 检查(差分贪心):从左到右扫,cur 为当前位置生效的新站数(差分数组 diff 维护)。
若 poweri + cur < x,则在位置 i+r(能覆盖 i 的最靠右建站点,尽量造福右侧)补 need 座站:
cur += need、diffmin(n-1, i+2r+1) -= need,累计 used += need;used > k 则失败。 - 复杂度 O(n log(Σstations + k))。
解法二:全枚举建站方案(对照,仅小数据) - 将 k 座站分配到 n 个位置(允许重复,组合数 C(n+k-1, n-1)),逐方案计算电量并取最小值的最大值。
- 用于小规模交叉验证。
复杂度: - 时间复杂度:O(n log(Σstations + k))
- 空间复杂度:O(n)
代码实现
java
import java.util.Arrays;
import java.util.Random;
public class Solution {
// 解法一:二分 + 差分贪心(主提交版本)
public long maxPower(int[] stations, int r, int k) {
int n = stations.length;
long[] power = new long[n];
long win = 0;
for (int i = 0; i <= r && i < n; i++) {
win += stations[i];
}
power[0] = win;
for (int i = 1; i < n; i++) {
int out = i - r - 1;
int in = i + r;
if (out >= 0) {
win -= stations[out];
}
if (in < n) {
win += stations[in];
}
power[i] = win;
}
long lo = 0, hi = (long) 3e14;
long ans = 0;
while (lo <= hi) {
long mid = (lo + hi) >>> 1;
if (check(power, r, k, mid)) {
ans = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return ans;
}
private boolean check(long[] power, int r, long k, long x) {
int n = power.length;
long[] diff = new long[n + 1];
long cur = 0;
long used = 0;
for (int i = 0; i < n; i++) {
cur += diff[i];
long have = power[i] + cur;
if (have < x) {
long need = x - have;
used += need;
if (used > k) {
return false;
}
cur += need; // 在 i+r 处建 need 座站,对当前位置立即生效
int end = i + 2 * r + 1; // 该站影响右边界 + 1
if (end < n) {
diff[end] -= need;
}
}
}
return true;
}
// 解法二:全枚举建站分配(对照,小数据 n <= 6, k <= 5)
public long maxPowerBrute(int[] stations, int r, int k) {
int n = stations.length;
long[] base = new long[n];
for (int i = 0; i < n; i++) {
for (int j = Math.max(0, i - r); j <= Math.min(n - 1, i + r); j++) {
base[i] += stations[j];
}
}
int[] build = new int[n]; // build[p] = 在 p 处建多少座新站
bestMin = -1;
enumerate(build, 0, (int) k, n, r, base);
return bestMin;
}
private long bestMin = -1;
private void enumerate(int[] build, int idx, int remain, int n, int r, long[] base) {
if (idx == n) {
if (remain != 0) {
return;
}
long[] pow = base.clone();
for (int p = 0; p < n; p++) {
for (int g = 0; g < build[p]; g++) {
for (int j = Math.max(0, p - r); j <= Math.min(n - 1, p + r); j++) {
pow[j]++;
}
}
}
long mn = pow[0];
for (long v : pow) {
mn = Math.min(mn, v);
}
bestMin = Math.max(bestMin, mn);
return;
}
for (int c = 0; c <= remain; c++) {
build[idx] = c;
enumerate(build, idx + 1, remain - c, n, r, base);
}
build[idx] = 0;
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.maxPower(new int[]{1, 2, 4, 5, 0}, 1, 2) == 5 : "2528 例1 主";
assert sol.maxPower(new int[]{4, 4, 4, 4}, 0, 3) == 4 : "2528 例2 主";
assert sol.maxPower(new int[]{1, 1, 1, 1}, 1, 0) == 2 : "2528 附加 k=0";
assert sol.maxPower(new int[]{0, 0, 0}, 1, 5) == 3 : "2528 附加 全零";
// 随机对拍(小数据全枚举验证)
Random rnd = new Random(2828);
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(6);
int[] st = new int[n];
for (int i = 0; i < n; i++) {
st[i] = rnd.nextInt(6);
}
int r = rnd.nextInt(n);
int k = rnd.nextInt(6);
long a = sol.maxPower(st, r, k);
long b = sol.maxPowerBrute(st, r, k);
if (a != b) {
System.out.println("对拍失败 " + Arrays.toString(st) + " r=" + r + " k=" + k + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2528 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n log(Σstations + k))
- 空间复杂度:O(n)
2529. Maximum Count of Positive Integer and Negative Integer(正整数和负整数的最大计数)
题目描述
给你一个按非递减顺序排列的数组 nums,返回正整数数目和负整数数目中的最大值。
如果 nums 中正整数的数目是 pos,而负整数的数目是 neg,返回 pos 和 neg 二者中的最大值。
注意:0 既不是正整数也不是负整数。
进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1
`nums = [-2,-1,-1,1,2,3]` → 3
示例 2
`nums = [-3,-2,-1,0,0,1,2]` → 3
示例 3
`nums = [5,20,66,1314]` → 4
提示:
1 <= nums.length <= 2000-2000 <= nums[i] <= 2000- 非递减顺序排列
解题思路
解法一:一次遍历计数(主提交版本)
- 遍历统计 neg(< 0)与 pos(> 0)的数量,返回较大者。O(n)。
解法二:二分查找边界(对照,满足 O(log n) 进阶) - 用手写二分求第一个 >= 0 的下标 firstNonNeg 与第一个 > 0 的下标 firstPos:
neg = firstNonNeg,pos = n - firstPos,返回 max。
复杂度: - 时间复杂度:O(n)(解法二 O(log n))
- 空间复杂度:O(1)
代码实现
java
import java.util.Arrays;
public class Solution {
// 解法一:一次遍历(主提交版本)
public int maximumCount(int[] nums) {
int neg = 0, pos = 0;
for (int x : nums) {
if (x < 0) {
neg++;
} else if (x > 0) {
pos++;
}
}
return Math.max(neg, pos);
}
// 解法二:二分查找边界(对照)
public int maximumCountBinary(int[] nums) {
int n = nums.length;
// 第一个 >= 0 的下标
int firstNonNeg = lowerBound(nums, 0);
// 第一个 > 0 的下标
int firstPos = upperBound(nums, 0);
int neg = firstNonNeg;
int pos = n - firstPos;
return Math.max(neg, pos);
}
private int lowerBound(int[] a, int target) {
int lo = 0, hi = a.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (a[mid] < target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
private int upperBound(int[] a, int target) {
int lo = 0, hi = a.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (a[mid] <= target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public static void main(String[] args) {
Solution sol = new Solution();
assert sol.maximumCount(new int[]{-2, -1, -1, 1, 2, 3}) == 3 : "2529 例1 主";
assert sol.maximumCountBinary(new int[]{-2, -1, -1, 1, 2, 3}) == 3 : "2529 例1 对照";
assert sol.maximumCount(new int[]{-3, -2, -1, 0, 0, 1, 2}) == 3 : "2529 例2 主";
assert sol.maximumCountBinary(new int[]{-3, -2, -1, 0, 0, 1, 2}) == 3 : "2529 例2 对照";
assert sol.maximumCount(new int[]{5, 20, 66, 1314}) == 4 : "2529 例3 主";
assert sol.maximumCountBinary(new int[]{5, 20, 66, 1314}) == 4 : "2529 例3 对照";
assert sol.maximumCount(new int[]{0, 0, 0}) == 0 : "2529 附加 全零";
assert sol.maximumCount(new int[]{-1, 0, 0}) == 1 : "2529 附加 一负";
// 随机对拍
java.util.Random rnd = new java.util.Random(2929);
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(60);
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = -5 + rnd.nextInt(11);
}
Arrays.sort(arr);
int a = sol.maximumCount(arr);
int b = sol.maximumCountBinary(arr);
if (a != b) {
System.out.println("对拍失败 " + Arrays.toString(arr) + " 主=" + a + " 对照=" + b);
System.exit(1);
}
}
System.out.println("2529 全部 PASS");
}
}
复杂度分析
- 时间复杂度:O(n)(解法二 O(log n))
- 空间复杂度:O(1)
2530. Maximal Score After Applying K Operations(执行 K 次操作后的最大分数)
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你的起始分数为 0 。
在一步操作中:选出一个满足 0 <= i < nums.length 的下标 i,将你的分数增加 nums[i],
并将 nums[i] 替换为 ceil(nums[i] / 3)(大于等于 val 的最小整数)。
返回在恰好执行 k 次操作后,你可能获得的最大分数。
示例 1
nums = [10,10,10,10,10], k = 5 -> 50
示例 2
nums = [1,10,3,3,3], k = 3 -> 17
提示:
- 1 <=
nums.length,k<= 10^5;1 <=nums[i]<= 10^9
解题思路
解法一:贪心 + 最大堆(主提交版本)
每次操作都应当选择当前值最大的元素,
因为它的贡献最大,且替换为 ceil(x/3) 后仍然可能继续被选中。
维护一个大顶堆,每轮弹出最大值加入分数,再把它除以 3 向上取整后放回。
解法二:排序 + 模拟,对照
将数组排序后,每次取出当前位置的最大值,其余用堆结构等价,
此处给出一个朴素的反复取最大值的实现用于对拍。
时间复杂度:解法一 O((n+k) log n);空间复杂度:O(n)。
代码实现
java
import java.util.PriorityQueue;
public class Solution {
public long maxKelements(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<Integer>((a, b) -> b - a);
for (int x : nums) {
pq.offer(x);
}
long score = 0;
for (int i = 0; i < k; i++) {
int cur = pq.poll();
score += cur;
pq.offer((cur + 2) / 3); // ceil(cur / 3)
}
return score;
}
public long maxKelementsBrute(int[] nums, int k) {
long[] a = new long[nums.length];
for (int i = 0; i < nums.length; i++) {
a[i] = nums[i];
}
long score = 0;
for (int t = 0; t < k; t++) {
int idx = 0;
for (int i = 1; i < a.length; i++) {
if (a[i] > a[idx]) {
idx = i;
}
}
score += a[idx];
a[idx] = (a[idx] + 2) / 3;
}
return score;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.maxKelements(new int[]{10, 10, 10, 10, 10}, 5) == 50 ? "PASS" : "FAIL");
System.out.println(s.maxKelements(new int[]{1, 10, 3, 3, 3}, 3) == 17 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(1);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(8);
int k = 1 + rnd.nextInt(12);
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = 1 + rnd.nextInt(30);
}
long r1 = s.maxKelements(a, k);
long r2 = s.maxKelementsBrute(a, k);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch: k=" + k + " arr=" + java.util.Arrays.toString(a)
+ " heap=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2530 全部 PASS" : "2530 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O((n+k) log n);空间复杂度:O(n)。
2531. Make Number of Distinct Characters Equal(使字符串中不同字符的数目相等)
题目描述
给你两个下标从 0 开始的字符串 word1 和 word2 。
一次移动由以下两个步骤组成:选中两个下标 i 和 j,分别满足 0 <= i < word1.length
和 0 <= j < word2.length,交换 word1[i] 和 word2[j]。
如果可以通过恰好一次移动,使 word1 和 word2 中不同字符的数目相等,则返回 true;否则返回 false。
示例 1
word1 = "ac", word2 = "b" -> false
示例 2
word1 = "abcc", word2 = "aab" -> true
示例 3
word1 = "abcde", word2 = "fghij" -> true
提示:
- 1 <=
word1.length,word2.length<= 10^5;两个字符串仅由小写英文字母组成。
解题思路
解法一:分类讨论计数(主提交版本)
统计两个字符串的字符频率。
交换一个字符对 (c1, c2)(c1 来自 word1、c2 来自 word2)之后,
word1 的字符集合变化为:若 c1 在 word1 中出现次数为 1(被移除后消失)则总数 -1;
若 c2 原本不在 word1 中则总数 +1。word2 同理。
枚举所有 26*26 的字符对,检查交换后两个不同字符数是否相等。
解法二:双集合暴力模拟,对照
构造字面字符串,尝试所有 i、j 交换后统计
不同字符数,仅用于小数据对拍。
时间复杂度:解法一 O(26^2)(读入 O(len));空间复杂度:O(1)。
代码实现
java
public class Solution {
public boolean isItPossible(String word1, String word2) {
int[] c1 = new int[26];
int[] c2 = new int[26];
for (char ch : word1.toCharArray()) c1[ch - 'a']++;
for (char ch : word2.toCharArray()) c2[ch - 'a']++;
int d1 = distinct(c1);
int d2 = distinct(c2);
for (int a = 0; a < 26; a++) {
if (c1[a] == 0) continue;
for (int b = 0; b < 26; b++) {
if (c2[b] == 0) continue;
// 从 word1 拿走 a、放入 b;从 word2 拿走 b、放入 a
int nd1 = d1;
int nd2 = d2;
if (c1[a] == 1) nd1--; // a 在 word1 中消失
if (c2[b] == 1) nd2--; // b 在 word2 中消失
if (c1[b] == 0) nd1++; // b 新出现在 word1
if (c2[a] == 0) nd2++; // a 新出现在 word2
if (a == b) {
// 交换的是同一个字符:两边集合都不变
nd1 = d1;
nd2 = d2;
}
if (nd1 == nd2) return true;
}
}
return false;
}
private int distinct(int[] cnt) {
int d = 0;
for (int c : cnt) if (c > 0) d++;
return d;
}
public boolean isItPossibleBrute(String word1, String word2) {
for (int i = 0; i < word1.length(); i++) {
for (int j = 0; j < word2.length(); j++) {
StringBuilder a = new StringBuilder(word1);
StringBuilder b = new StringBuilder(word2);
char x = a.charAt(i);
char y = b.charAt(j);
a.setCharAt(i, y);
b.setCharAt(j, x);
if (distinctStr(a.toString()) == distinctStr(b.toString())) return true;
}
}
return false;
}
private int distinctStr(String s) {
boolean[] seen = new boolean[26];
for (char ch : s.toCharArray()) seen[ch - 'a'] = true;
int d = 0;
for (boolean b : seen) if (b) d++;
return d;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(!s.isItPossible("ac", "b") ? "PASS" : "FAIL");
System.out.println(s.isItPossible("abcc", "aab") ? "PASS" : "FAIL");
System.out.println(s.isItPossible("abcde", "fghij") ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(2);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
StringBuilder a = new StringBuilder();
StringBuilder b = new StringBuilder();
int la = 1 + rnd.nextInt(6);
int lb = 1 + rnd.nextInt(6);
for (int i = 0; i < la; i++) a.append((char) ('a' + rnd.nextInt(4)));
for (int i = 0; i < lb; i++) b.append((char) ('a' + rnd.nextInt(4)));
boolean r1 = s.isItPossible(a.toString(), b.toString());
boolean r2 = s.isItPossibleBrute(a.toString(), b.toString());
if (r1 != r2) {
ok = false;
System.out.println("Mismatch: w1=" + a + " w2=" + b + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2531 全部 PASS" : "2531 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O(26^2)(读入 O(len));空间复杂度:O(1)。
2532. Time to Cross a Bridge(过桥的时间)
题目描述
共有 k 位工人计划将 n 个箱子从右侧的(旧)仓库移动到左侧的(新)仓库。
给你两个整数 n 和 k,以及一个二维整数数组 time,大小为 k x 4,
其中 time[i] = `right_i`, `pick_i`, `left_i`, `put_i`:
- 从左岸(新仓库)跨过桥到右岸(旧仓库),用时
right_i分钟; - 从旧仓库选择一个箱子并返回到桥边,用时
pick_i分钟(不同工人可同时搬箱); - 从右岸(旧仓库)跨过桥到左岸(新仓库),用时
left_i分钟; - 将箱子放入新仓库并返回到桥边,用时
put_i分钟(不同工人可同时放箱)。
若left_i+right_i>left_j+right_j,或相等且i>j,则称工人i效率低于工人j。
过桥规则:同时只能有一名工人过桥;当桥梁未被使用时,优先让右侧「效率最低」的工人
(已经拿起盒子的工人)过桥,否则优先让左侧「效率最低」的工人通过;
如果左侧已经派出足够的工人来拾取所有剩余的箱子,则不会再从左侧派出工人。
返回最后一个箱子到达桥左侧的时间。
示例 1
n = 1, k = 3, time = [[1,1,2,1],[1,1,3,1],[1,1,4,1]] -> 6
示例 2
n = 3, k = 2, time = [[1,5,1,8],[10,10,10,10]] -> 37
提示:
- 1 <=
n,k<= 10^4;1 <=left_i,pick_i,right_i,put_i<= 1000
解题思路
解法一:双优先队列 + 事件队列模拟(主提交版本)
- left / right 两个等待堆:按「效率低者优先」(left+right 大、下标大)排序;
- events 事件堆按完成时间升序:(完成时刻, 工人, 类型),类型 0 = 放完箱子回到左侧等待,
类型 1 = 取完箱子回到右侧等待; - 每轮先处理所有已到期事件(把工人放回对应等待堆);
桥空闲时:右侧有等待工人则让效率最低者过桥到左岸(耗时 left_i,记录答案 = 桥到达时刻),
否则若还有箱子未取且左侧有等待工人,派效率最低者过桥到右岸(耗时 right_i,取箱数 +1);
若两边都无人可动,把时间推进到下一个事件发生时刻。
答案 = 最后一个从右岸返回左岸的过桥完成时刻(每个右→左过桥工人恰好携带一个箱子)。
解法二:分钟级暴力模拟,对照
小数据下逐分钟推进,同一时刻先结算已完成的取/放箱,
再看桥是否空闲、按规则选人过桥,得到答案用于对拍。
时间复杂度:解法一 O((n + k) log k);空间复杂度:O(k)。
代码实现
java
import java.util.PriorityQueue;
public class Solution {
public int findCrossingTime(int n, int k, int[][] time) {
long[] eff = new long[k];
for (int i = 0; i < k; i++) {
eff[i] = (long) time[i][0] + time[i][2]; // right + left
}
PriorityQueue<Integer> left = new PriorityQueue<Integer>((a, b) ->
eff[a] != eff[b] ? (eff[a] > eff[b] ? -1 : 1) : (b - a));
PriorityQueue<Integer> right = new PriorityQueue<Integer>((a, b) ->
eff[a] != eff[b] ? (eff[a] > eff[b] ? -1 : 1) : (b - a));
for (int i = 0; i < k; i++) left.offer(i);
// events: [完成时刻, 工人, 类型] 类型 0 = 放完箱子回左侧;1 = 取完箱子回右侧
PriorityQueue<long[]> events = new PriorityQueue<long[]>((a, b) ->
a[0] != b[0] ? (a[0] < b[0] ? -1 : 1) : (int) (a[1] - b[1]));
long cur = 0;
int remaining = n; // 尚未被派出取箱的箱子数
long ans = 0; // 最后一个右->左过桥完成时刻
while (remaining > 0 || !right.isEmpty() || !events.isEmpty()) {
// 1. 结算所有已到期事件
while (!events.isEmpty() && events.peek()[0] <= cur) {
long[] ev = events.poll();
int w = (int) ev[1];
if (ev[2] == 0) left.offer(w);
else right.offer(w);
}
// 2. 桥空闲时选人过桥
if (!right.isEmpty()) {
int w = right.poll();
cur += time[w][2]; // 右 -> 左,携带一个箱子
ans = cur; // 该箱子到达桥左侧的时刻
events.offer(new long[]{cur + time[w][3], w, 0}); // 放箱后回左侧
} else if (remaining > 0 && !left.isEmpty()) {
int w = left.poll();
cur += time[w][0]; // 左 -> 右,去取一个箱子
remaining--;
events.offer(new long[]{cur + time[w][1], w, 1}); // 取箱后回右侧
} else {
// 无人可动:推进到最近的事件时刻
long next = Long.MAX_VALUE;
if (!events.isEmpty()) next = Math.min(next, events.peek()[0]);
if (next == Long.MAX_VALUE) break;
cur = next;
}
}
return (int) ans;
}
public int findCrossingTimeBrute(int n, int k, int[][] time) {
// side[w] = 0 左岸 / 1 右岸;-1 = 正桥上空闲等待
int[] side = new int[k];
// -1 = 空闲;0 = 正在放箱(完成时刻 done[w]);1 = 正在取箱
int[] act = new int[k];
long[] done = new long[k];
// 桥状态:busy 表示桥正被占用,busyUntil 为释放时刻
boolean busy = false;
long busyUntil = 0;
long arrived = 0; // 已到达左岸的箱子数
long picked = 0; // 已派出取箱的总次数
long t = 0;
// 效率:越大越优先过桥
long[] eff = new long[k];
for (int i = 0; i < k; i++) eff[i] = (long) time[i][0] + time[i][2];
while (arrived < n) {
// 同秒动作:结算完成的取/放箱
for (int w = 0; w < k; w++) {
if (act[w] == 0 && done[w] <= t) { act[w] = -1; side[w] = 0; } // 放完,回左岸等待
if (act[w] == 1 && done[w] <= t) { act[w] = -1; side[w] = 1; } // 取完,回右岸等待
}
if (busy && busyUntil <= t) busy = false;
// 若桥空闲,按规则选人:右侧优先(效率最低者),左其次
if (!busy) {
int bestRight = -1;
for (int w = 0; w < k; w++) {
if (act[w] == -1 && side[w] == 1) {
if (bestRight == -1 || eff[w] > eff[bestRight]
|| (eff[w] == eff[bestRight] && w > bestRight)) bestRight = w;
}
}
if (bestRight >= 0) {
int w = bestRight;
busy = true;
busyUntil = t + time[w][2]; // 右 -> 左过桥
act[w] = 0;
side[w] = 0;
done[w] = busyUntil + time[w][3]; // 放箱完成时刻
arrived++; // 一个箱子到达左岸
if (arrived == n) return (int) busyUntil; // 最后一个箱子桥左侧时刻
} else if (picked < n) {
int bestLeft = -1;
for (int w = 0; w < k; w++) {
if (act[w] == -1 && side[w] == 0) {
if (bestLeft == -1 || eff[w] > eff[bestLeft]
|| (eff[w] == eff[bestLeft] && w > bestLeft)) bestLeft = w;
}
}
if (bestLeft >= 0) {
int w = bestLeft;
busy = true;
busyUntil = t + time[w][0]; // 左 -> 右过桥
picked++;
act[w] = 1;
side[w] = 1;
done[w] = busyUntil + time[w][1]; // 取箱完成时刻
}
}
}
t++;
}
return (int) t;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
int r1 = s.findCrossingTime(1, 3, new int[][]{{1,1,2,1},{1,1,3,1},{1,1,4,1}});
System.out.println(r1 + (r1 == 6 ? " PASS" : " FAIL expect 6"));
int r2 = s.findCrossingTime(3, 2, new int[][]{{1,5,1,8},{10,10,10,10}});
System.out.println(r2 + (r2 == 37 ? " PASS" : " FAIL expect 37"));
// 随机对拍
java.util.Random rnd = new java.util.Random(3);
boolean ok = true;
for (int t = 0; t < 1000; t++) {
int k = 1 + rnd.nextInt(4);
int n = 1 + rnd.nextInt(5);
int[][] time = new int[k][4];
for (int i = 0; i < k; i++) {
for (int j = 0; j < 4; j++) time[i][j] = 1 + rnd.nextInt(4);
}
int r1x = s.findCrossingTime(n, k, time);
int r2x = s.findCrossingTimeBrute(n, k, time);
if (r1x != r2x) {
ok = false;
System.out.println("Mismatch n=" + n + " k=" + k);
for (int i = 0; i < k; i++) System.out.println(" " + java.util.Arrays.toString(time[i]));
System.out.println(" main=" + r1x + " brute=" + r2x);
break;
}
}
System.out.println(ok ? "2532 全部 PASS" : "2532 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O((n + k) log k);空间复杂度:O(k)。
2533. Number of Good Binary Strings(好二进制字符串的数量)
题目描述
给你四个整数 minLength、maxLength、oneGroup 和 zeroGroup。
好二进制字符串满足:
- 长度在 `minLength`, `maxLength` 之间;
- 每块连续 1 的个数是
oneGroup的整数倍; - 每块连续 0 的个数是
zeroGroup的整数倍(0 可以被认为是所有数字的倍数)。
返回好二进制字符串的个数,答案对 10^9 + 7 取余。
示例 1
minLength = 2, maxLength = 3, oneGroup = 1, zeroGroup = 2 -> 5
示例 2
minLength = 4, maxLength = 4, oneGroup = 4, zeroGroup = 3 -> 1
提示:
- 1 <=
minLength<=maxLength<= 10^5;1 <=oneGroup,zeroGroup<=maxLength
解题思路
解法一:递推 / DP(主提交版本)
用 f0i 表示以 0 结尾、长度为 i 的好字符串个数,f1i 表示以 1 结尾的个数。
末尾一段连续 0 的长度必须是 zeroGroup 的整数倍且长度记为 t,
之前的位置长度为 i - t,且以 1 结尾(或 t == i 表示整串全是 0):
f0i = sum_{g>=1, t=gzeroGroup<=i} ( i-t==0 ? 1 : f1i-t )
f1i = sum_{g>=1, t=g oneGroup<=i} ( i-t==0 ? 1 : f0i-t )
令 s0i = f00+f01+...+f0i,则 f0i = s1i-zeroGroup + s1i-2*zeroGroup + ...
其中 s1j = 1 + f11 + ... + f1j(s10 = 1 表示空串前缀以 0 结尾视为合法)。
为 O(1) 更新,我们维护「前缀和辅助」:每次递推 f0i 时需要 f1 下标 j = i - g* zeroGroup
处的累计。简单实现:前缀和 s1j = (累计 f1 到 j) + 1(把 j=0 空串计数为 1)。
这样 f0i = sum over valid t of prefix1i - t,遍历 g 从 1 开始逐段累加,
由于段长是 oneGroup/zeroGroup 的倍数,最多 i/group 项,最坏 O(N^2/group)。
优化:令 fs1i 表示所有下标对 group 取模为 i % group 的 s1 之和(滚动)。
实际上 f0i 只取决于和 i 同余(模 zeroGroup)的 s1 前缀累计。
记 Ai = Ai-zeroGroup + s1i-zeroGroup,那么 f0i 可由 i-zeroGroup 的累计递推:
f0i = Ai - zeroGroup,其中 Ai = Ai-zeroGroup + s1i。
同理 f1i = Bi - oneGroup,Bi = Bi-oneGroup + s0i。
其中 s0i = n0i(f0 前缀和)+ 1(空串),s1i = n1i + 1。
最终答案 = sum_{i=minLength}^{maxLength} (f0i + f1i)。
解法二:带记忆化搜索 / 直接 01 枚举,对照
按长度 i 枚举首个块是 0 还是 1,
块长必须是 group 的整数倍,暴力递归统计,仅用于小数据对拍。
时间复杂度:解法一 O(maxLength);空间复杂度:O(maxLength)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int goodBinaryStrings(int minLength, int maxLength, int oneGroup, int zeroGroup) {
// f0[i] 以 0 结尾的好字符串个数;f1[i] 以 1 结尾的好字符串个数
long[] f0 = new long[maxLength + 1];
long[] f1 = new long[maxLength + 1];
// P1[i] = sum_{j ≡ i (mod zeroGroup), 1 <= j <= i-1} f1[j]
// P0[i] = sum_{j ≡ i (mod oneGroup), 1 <= j <= i-1} f0[j]
long[] P1 = new long[maxLength + 1];
long[] P0 = new long[maxLength + 1];
long ans = 0;
for (int i = 1; i <= maxLength; i++) {
if (i >= zeroGroup) P1[i] = (P1[i - zeroGroup] + f1[i - zeroGroup]) % MOD;
if (i >= oneGroup) P0[i] = (P0[i - oneGroup] + f0[i - oneGroup]) % MOD;
f0[i] = ((i % zeroGroup == 0 ? 1L : 0L) + P1[i]) % MOD;
f1[i] = ((i % oneGroup == 0 ? 1L : 0L) + P0[i]) % MOD;
if (i >= minLength) ans = (ans + f0[i] + f1[i]) % MOD;
}
return (int) ans;
}
public int goodBinaryStringsBrute(int minLength, int maxLength, int oneGroup, int zeroGroup) {
int cnt = 0;
for (int len = minLength; len <= maxLength; len++) {
int total = 1 << len;
for (int mask = 0; mask < total; mask++) {
if (isGood(mask, len, oneGroup, zeroGroup)) cnt++;
}
}
return cnt;
}
private boolean isGood(int mask, int len, int og, int zg) {
int run = 0; // 0 = 尚未开始;正 = 当前连续 1 的个数;负 = 当前连续 0 的个数
for (int i = 0; i < len; i++) {
int bit = (mask >> (len - 1 - i)) & 1;
if (bit == 1) {
if (run < 0) { // 结束一段 0
if ((-run) % zg != 0) return false;
run = 1;
} else {
run = (run > 0 ? run + 1 : 1);
}
} else {
if (run > 0) { // 结束一段 1
if (run % og != 0) return false;
run = -1;
} else {
run = (run < 0 ? run - 1 : -1);
}
}
}
if (run > 0 && run % og != 0) return false;
if (run < 0 && (-run) % zg != 0) return false;
return true;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.goodBinaryStrings(2, 3, 1, 2) == 5 ? "PASS" : "FAIL");
System.out.println(s.goodBinaryStrings(4, 4, 4, 3) == 1 ? "PASS" : "FAIL");
// 随机对拍(小数据暴力验证)
java.util.Random rnd = new java.util.Random(4);
boolean ok = true;
for (int t = 0; t < 500; t++) {
int minL = 1 + rnd.nextInt(8);
int maxL = minL + rnd.nextInt(6);
int og = 1 + rnd.nextInt(5);
int zg = 1 + rnd.nextInt(5);
int r1 = s.goodBinaryStrings(minL, maxL, og, zg);
int r2 = s.goodBinaryStringsBrute(minL, maxL, og, zg);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch minL=" + minL + " maxL=" + maxL
+ " og=" + og + " zg=" + zg + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2533 全部 PASS" : "2533 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O(maxLength);空间复杂度:O(maxLength)。
2534. Time Taken to Cross the Door(通过门的时间)
题目描述
n 个人按 0 到 n-1 编号,有一扇门,每个人只能通过门一次,耗时一秒。
给你非递减的 arrival 数组(arrival[i] 是第 i 个人到达门前的时间)和 state 数组
(state[i] = 0 表示想进入,1 表示想离开)。
若同时有多人想用门,规则:
- 前一秒没有使用门 -> 想离开的人先离开;
- 前一秒是进入 -> 想进入的人先进入;
- 前一秒是离开 -> 想离开的人先离开;
- 同方向多人 -> 编号最小者先。
返回数组answer,answer[i]是第i个人通过门的时刻。
示例 1
arrival = [0,1,1,2,4], state = [0,1,0,0,1] -> [0,3,1,2,4]
提示:
n== arrival.length == state.length,1 <=n<= 10^5;- 0 <=
arrival[i]<= 10^5,arrival非递减;state[i]∈ {0,1}
解题思路
解法一:双队列模拟(主提交版本)
用数组排队:把当前时刻及之前到达、尚未进/出门的人按 state 分别放入两个队列
(队列里是同方向的人,按到达顺序即编号顺序排队)。
每秒钟:若上一秒有人进/出门,优先放行与上秒同向的队列(若空则放逆向);
否则(上秒无人用门)优先放行「离开」队列(若空则放「进入」队列)。
优先队列为空时,时间快进到下一个 arrival。
解法二:事件表暴力模拟,对照
逐秒推进,每次扫描所有未处理人员,选择当前符合条件的
最优者(方向优先级 + 最小编号),小规模对拍用。
时间复杂度:解法一 O(n);空间复杂度:O(n)。
代码实现
java
import java.util.ArrayDeque;
public class Solution {
public int[] timeTaken(int[] arrival, int[] state) {
int n = arrival.length;
int[] ans = new int[n];
ArrayDeque<Integer> enter = new ArrayDeque<Integer>(); // state=0
ArrayDeque<Integer> exit = new ArrayDeque<Integer>(); // state=1
int t = 0; // 当前秒
int idx = 0; // 下一个到达的人
int lastDir = -1; // 上一秒使用门的方向,-1 表示没有人用过
while (idx < n || !enter.isEmpty() || !exit.isEmpty()) {
// 把 t 时刻及之前到达的人入队
while (idx < n && arrival[idx] <= t) {
if (state[idx] == 0) enter.offer(idx);
else exit.offer(idx);
idx++;
}
int dir = -1;
if (lastDir == 0) { // 上一秒进入
if (!enter.isEmpty()) dir = 0;
else if (!exit.isEmpty()) dir = 1;
} else if (lastDir == 1) { // 上一秒离开
if (!exit.isEmpty()) dir = 1;
else if (!enter.isEmpty()) dir = 0;
} else { // 上秒无人用门
if (!exit.isEmpty()) dir = 1;
else if (!enter.isEmpty()) dir = 0;
}
if (dir == -1) {
// 本秒无人可用:快进到下一个人的到达时刻
if (idx < n) t = arrival[idx];
lastDir = -1; // 空档期间无人用门,重置记忆
continue;
}
int person = (dir == 0) ? enter.poll() : exit.poll();
ans[person] = t;
lastDir = dir;
t++;
}
return ans;
}
public int[] timeTakenBrute(int[] arrival, int[] state) {
int n = arrival.length;
int[] ans = new int[n];
boolean[] done = new boolean[n];
int t = 0;
int lastDir = -1;
int left = n;
while (left > 0) {
// 选择:可到的人里先按方向优先级,再按编号最小
int dir = -1;
int person = -1;
// 确定方向优先级
int pref = -1;
if (lastDir == -1) pref = 1;
else pref = lastDir;
// 第一遍按 pref
for (int cand = 0; cand < n; cand++) {
if (!done[cand] && arrival[cand] <= t && state[cand] == pref) {
dir = pref;
person = cand;
break;
}
}
if (person == -1) {
for (int cand = 0; cand < n; cand++) {
if (!done[cand] && arrival[cand] <= t) {
dir = state[cand];
person = cand;
break;
}
}
}
if (person == -1) {
// 无人到:快进
int nt = Integer.MAX_VALUE;
for (int i = 0; i < n; i++) {
if (!done[i] && arrival[i] > t) nt = Math.min(nt, arrival[i]);
}
t = Math.max(t + 1, nt);
lastDir = -1;
continue;
}
ans[person] = t;
done[person] = true;
lastDir = dir;
t++;
left--;
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
int[] r = s.timeTaken(new int[]{0, 1, 1, 2, 4}, new int[]{0, 1, 0, 0, 1});
System.out.println(java.util.Arrays.toString(r) + (java.util.Arrays.equals(r, new int[]{0, 3, 1, 2, 4}) ? " PASS" : " FAIL"));
// 随机对拍
java.util.Random rnd = new java.util.Random(5);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(8);
int[] arr = new int[n];
int[] st = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = (i == 0) ? rnd.nextInt(3) : arr[i - 1] + rnd.nextInt(3);
st[i] = rnd.nextInt(2);
}
int[] r1 = s.timeTaken(arr, st);
int[] r2 = s.timeTakenBrute(arr, st);
if (!java.util.Arrays.equals(r1, r2)) {
ok = false;
System.out.println("Mismatch arr=" + java.util.Arrays.toString(arr)
+ " st=" + java.util.Arrays.toString(st)
+ " main=" + java.util.Arrays.toString(r1)
+ " brute=" + java.util.Arrays.toString(r2));
break;
}
}
System.out.println(ok ? "2534 全部 PASS" : "2534 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O(n);空间复杂度:O(n)。
2535. Difference Between Element Sum and Digit Sum of an Array(数组元素和与数字和的绝对差)
题目描述
给你一个正整数数组 nums。
元素和是 nums 中的所有元素相加求和;数字和是 nums 中每一个元素的每一数位
(重复数位需多次求和)相加求和。返回元素和与数字和的绝对差。
示例 1
nums = [1,15,6,3] -> 9
示例 2
nums = [1,2,3,4] -> 0
提示:
- 1 <=
nums.length<= 2000;1 <=nums[i]<= 2000
解题思路
解法一:一次遍历(主提交版本)
对每个数累加其本身到元素和,循环除以 10 取余累加各位数字到数字和,
最后返回 |元素和 - 数字和|。
解法二:字符串写法,对照
将每个数转成字符串逐位求和,逻辑等价、便于交叉验证。
时间复杂度:O(n log max(numsi));空间复杂度:O(1)。
代码实现
java
public class Solution {
public int differenceOfSum(int[] nums) {
int eleSum = 0;
int digitSum = 0;
for (int x : nums) {
eleSum += x;
int v = x;
while (v > 0) {
digitSum += v % 10;
v /= 10;
}
}
return Math.abs(eleSum - digitSum);
}
public int differenceOfSumStr(int[] nums) {
int eleSum = 0;
int digitSum = 0;
for (int x : nums) {
eleSum += x;
String s = String.valueOf(x);
for (int i = 0; i < s.length(); i++) {
digitSum += s.charAt(i) - '0';
}
}
return Math.abs(eleSum - digitSum);
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.differenceOfSum(new int[]{1, 15, 6, 3}) == 9 ? "PASS" : "FAIL");
System.out.println(s.differenceOfSum(new int[]{1, 2, 3, 4}) == 0 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(6);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(20);
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(100000);
if (s.differenceOfSum(a) != s.differenceOfSumStr(a)) {
ok = false;
System.out.println("Mismatch: " + java.util.Arrays.toString(a));
break;
}
}
System.out.println(ok ? "2535 全部 PASS" : "2535 存在不一致");
}
}
复杂度分析
时间复杂度:O(n log max(numsi));空间复杂度:O(1)。
2536. Increment Submatrices by One(子矩阵元素加 1)
题目描述
给你一个正整数 n,表示最初有一个 n x n、下标从 0 开始的整数矩阵 mat,矩阵中填满了 0。
另给你一个二维整数数组 query,针对每个查询 query[i] = `row1_i`, `col1_i`, `row2_i`, `col2_i`,
将左上角 (row1_i, col1_i)、右下角 (row2_i, col2_i) 的子矩阵中每个元素加 1。
返回执行完所有操作后得到的矩阵。
示例 1
n = 3, queries = [[1,1,2,2],[0,0,1,1]] -> [[1,1,0],[1,2,1],[0,1,1]]
示例 2
n = 2, queries = [[0,0,1,1]] -> [[1,1],[1,1]]
提示:
- 1 <=
n<= 500;1 <= queries.length <= 10^4; - 0 <=
row1_i<=row2_i<n;0 <=col1_i<=col2_i<n
解题思路
解法一:二维差分(主提交版本)
对每个矩形区域做差分:diffr1c1++, diffr2+1c1--,
diffr1c2+1--, diffr2+1c2+1++;最后对 diff 做两次前缀和还原矩阵,
每个 query 只需 O(1) 时间。
解法二:暴力逐格加 1,对照
直接对每个查询的四重循环累加,小数据下输出相同。
时间复杂度:解法一 O(n^2 + q);空间复杂度:O(n^2)。
代码实现
java
public class Solution {
public int[][] rangeAddQueries(int n, int[][] queries) {
int[][] diff = new int[n + 1][n + 1];
for (int[] q : queries) {
int r1 = q[0], c1 = q[1], r2 = q[2], c2 = q[3];
diff[r1][c1]++;
diff[r2 + 1][c1]--;
diff[r1][c2 + 1]--;
diff[r2 + 1][c2 + 1]++;
}
int[][] mat = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int v = diff[i][j];
if (i > 0) v += mat[i - 1][j];
if (j > 0) v += mat[i][j - 1];
if (i > 0 && j > 0) v -= mat[i - 1][j - 1];
mat[i][j] = v;
}
}
return mat;
}
public int[][] rangeAddQueriesBrute(int n, int[][] queries) {
int[][] mat = new int[n][n];
for (int[] q : queries) {
for (int i = q[0]; i <= q[2]; i++) {
for (int j = q[1]; j <= q[3]; j++) {
mat[i][j]++;
}
}
}
return mat;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
int[][] r1 = s.rangeAddQueries(3, new int[][]{{1, 1, 2, 2}, {0, 0, 1, 1}});
System.out.println(java.util.Arrays.deepToString(r1));
System.out.println("示例1 " + (java.util.Arrays.deepEquals(r1, new int[][]{{1, 1, 0}, {1, 2, 1}, {0, 1, 1}}) ? "PASS" : "FAIL"));
int[][] r2 = s.rangeAddQueries(2, new int[][]{{0, 0, 1, 1}});
System.out.println("示例2 " + (java.util.Arrays.deepEquals(r2, new int[][]{{1, 1}, {1, 1}}) ? "PASS" : "FAIL"));
// 随机对拍
java.util.Random rnd = new java.util.Random(7);
boolean ok = true;
for (int t = 0; t < 1000; t++) {
int n = 1 + rnd.nextInt(7);
int q = 1 + rnd.nextInt(10);
int[][] qs = new int[q][4];
for (int[] qu : qs) {
int a = rnd.nextInt(n), b = rnd.nextInt(n);
int c = a + rnd.nextInt(n - a), d = b + rnd.nextInt(n - b);
qu[0] = a; qu[1] = b; qu[2] = c; qu[3] = d;
}
int[][] m1 = s.rangeAddQueries(n, qs);
int[][] m2 = s.rangeAddQueriesBrute(n, qs);
if (!java.util.Arrays.deepEquals(m1, m2)) {
ok = false;
System.out.println("Mismatch n=" + n + " qs=" + java.util.Arrays.deepToString(qs));
System.out.println("main=" + java.util.Arrays.deepToString(m1));
System.out.println("brute=" + java.util.Arrays.deepToString(m2));
break;
}
}
System.out.println(ok ? "2536 全部 PASS" : "2536 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O(n^2 + q);空间复杂度:O(n^2)。
2537. Count the Number of Good Subarrays(统计好子数组的数目)
题目描述
给你一个整数数组 nums 和一个整数 k,返回 nums 中「好」子数组的数目。
一个子数组 arr 如果有至少 k 对下标 (i, j) 满足 i < j 且 arr[i] == arr[j],
那么称它是一个好子数组。子数组是原数组中一段连续非空的元素序列。
示例 1
nums = [1,1,1,1,1], k = 10 -> 1
示例 2
nums = [3,1,4,3,2,2,4], k = 2 -> 4
提示:
- 1 <=
nums.length<= 10^5;1 <=nums[i],k<= 10^9
解题思路
解法一:滑动窗口(主提交版本)
窗口内相同的数对数为 pairs = Σ C(freqv, 2)。右端 r 每扩展一格,
若 numsr 之前已出现 freq 次,则新增 freq 对,pairs += freq。
当 pairs >= k 时,对固定的左端 l,右端取 r...n-1 的任意值都满足条件,
一次累加 n - r 个,然后收缩左端 l(移出元素后 pairs 减少 freq-1 对)。
复杂度 O(n)。
解法二:暴力枚举,对照
枚举所有子数组,统计每对相等下标,O(n^2) 仅用于小数据对拍。
时间复杂度:解法一 O(n);空间复杂度:O(n)。
代码实现
java
import java.util.HashMap;
public class Solution {
public long countGood(int[] nums, int k) {
int n = nums.length;
HashMap<Integer, Integer> freq = new HashMap<Integer, Integer>();
long pairs = 0;
long ans = 0;
int l = 0;
for (int r = 0; r < n; r++) {
int f = freq.getOrDefault(nums[r], 0);
pairs += f; // 与前面 f 个相同元素各组成一对
freq.put(nums[r], f + 1);
while (pairs >= k) {
ans += n - r; // 所有以 l 开头、右端点 >= r 的子数组均满足
int fl = freq.get(nums[l]);
pairs -= fl - 1; // 移除 nums[l] 后减少 fl-1 对
freq.put(nums[l], fl - 1);
l++;
}
}
return ans;
}
public long countGoodBrute(int[] nums, int k) {
int n = nums.length;
long ans = 0;
for (int i = 0; i < n; i++) {
HashMap<Integer, Integer> cnt = new HashMap<Integer, Integer>();
long pairs = 0;
for (int j = i; j < n; j++) {
int f = cnt.getOrDefault(nums[j], 0);
pairs += f;
cnt.put(nums[j], f + 1);
if (pairs >= k) ans++;
}
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.countGood(new int[]{1, 1, 1, 1, 1}, 10) == 1 ? "PASS" : "FAIL");
System.out.println(s.countGood(new int[]{3, 1, 4, 3, 2, 2, 4}, 2) == 4 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(8);
boolean ok = true;
for (int t = 0; t < 1000; t++) {
int n = 1 + rnd.nextInt(30);
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = rnd.nextInt(6);
int k = 1 + rnd.nextInt(15);
long r1 = s.countGood(a, k);
long r2 = s.countGoodBrute(a, k);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch arr=" + java.util.Arrays.toString(a)
+ " k=" + k + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2537 全部 PASS" : "2537 存在不一致");
}
}
复杂度分析
时间复杂度:解法一 O(n);空间复杂度:O(n)。
2538. Difference Between Maximum and Minimum Price Sum(最大价值和与最小价值和的差值)
题目描述
给你一个 n 个节点的无向无根树,节点编号 0 到 n-1,edges 表示树边。
每个节点都有价值 price[i]。一条路径的价值和是路径上所有节点价值之和。
你可以选择树中任意一个节点作为根节点 root。选择 root 为根的开销是:
以 root 为起点的所有路径中,价值和最大的一条路径与最小的一条路径的差值。
返回所有节点作为根的选择中,最大的开销。
示例 1
n = 6, edges = [[0,1],[1,2],[1,3],[3,4],[3,5]], price = [9,8,7,6,10,5] -> 24
示例 2
n = 3, edges = [[0,1],[1,2]], price = [1,1,1] -> 2
提示:
- 1 <=
n<= 10^5;edges.length==n-1;1 <=price[i]<= 10^5
解题思路
解法一:树形 DP / 树的直径(主提交版本)
开销 = 以 root 为端点的一条「最大权和路径」减去「单节点最小路径」(只含 root 自己)。
因为最小路径显然可以取只含根节点的路径(价值 priceroot),而最大路径任意。
所以问题等价于:求树上以任意节点为端点的带权路径(点权和)的最大值,再减去该端点价值,
再对所有端点取最大 ------ 也就是求树的带权直径 D,答案 = D - min_price 吗?
不对:最小路径必须是以 root 为起点(即 root 是端点的路径),且最小路径取自 root 的单点。
因此开销 = (以 root 为一端的最大路径价值和) - priceroot。
记 downu = 从 u 向下(子树中)出发的最大路径价值和(包含 u)。
以 u 为根(假设树的根固定为 0,但任意节点均可做为路径端点),
「以 u 为端点的最大路径和」= max(down 子节点 + priceu),即从 u 走到子树最深。
而整棵树中「以 u 为一端」的最大值还要考虑向上走,这里通过换根 DP:
upu = 从 u 向上走能获得的最大路径和(包含 u)。
最终 ans = max over u of (max(downPath(u), upPath(u)) - priceu)。
其中 downPath(u) = max(priceu, maxChild(downc + priceu)),
upPath(u) = priceu + max(0, upPath(parent-去child方向))。
实现上:第一次 dfs 计算 downu;第二次 dfs 换根,upchild =
pricechild + max(0, max(upu - priceu必含u, downsib - priceu... )) 实际:
从 u 到 child 方向之外的「走出 u 的最大路径」bestOut =
max(priceu 单点, upu(从 u 向上), max over other children c' of downc'),
upchild = bestOut + pricechild。
其中 upu 表示「从 u 向上(父方向)走的最大路径和,包含 u」。
解法二:暴力对拍
小数据下枚举每个根,跑 DFS 求所有路径价值和的 max 与 min。
时间复杂度:O(n);空间复杂度:O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public long maxOutput(int n, int[][] edges, int[] price) {
List<Integer>[] g = new List[n];
for (int i = 0; i < n; i++) g[i] = new ArrayList<Integer>();
for (int[] e : edges) {
g[e[0]].add(e[1]);
g[e[1]].add(e[0]);
}
long[] down = new long[n]; // 以 0 为根,向下走的最大路径和(含自身)
dfsDown(0, -1, g, price, down);
// 换根求 up:up[u] = 从 u 向父方向走的最大路径和(含 u)
long[] up = new long[n];
dfsUp(0, -1, g, price, down, up);
long ans = 0;
for (int u = 0; u < n; u++) {
long best = Math.max(down[u], up[u]);
ans = Math.max(ans, best - price[u]);
}
return ans;
}
private void dfsDown(int u, int p, List<Integer>[] g, int[] price, long[] down) {
long best = price[u];
for (int v : g[u]) {
if (v == p) continue;
dfsDown(v, u, g, price, down);
best = Math.max(best, down[v] + price[u]);
}
down[u] = best;
}
private void dfsUp(int u, int p, List<Integer>[] g, int[] price, long[] down, long[] up) {
// 先处理 p 的方向(向上)+ 兄弟子树的 bestOut
for (int v : g[u]) {
if (v == p) continue;
// 从 u 出发不经过 v 所能得到的最大路径和(包含 u)
long bestOut = price[u];
if (p != -1) bestOut = Math.max(bestOut, up[u]);
for (int c : g[u]) {
if (c == p || c == v) continue;
bestOut = Math.max(bestOut, down[c] + price[u]);
}
up[v] = bestOut + price[v];
dfsUp(v, u, g, price, down, up);
}
}
public long maxOutputBrute(int n, int[][] edges, int[] price) {
List<Integer>[] g = new List[n];
for (int i = 0; i < n; i++) g[i] = new ArrayList<Integer>();
for (int[] e : edges) {
g[e[0]].add(e[1]);
g[e[1]].add(e[0]);
}
long ans = 0;
// 对每个 root,DFS 它的所有路径(枚举路径另一端),路径和 min 一定取 root 单点,
// 因此只需求以 root 为端点的最大路径和。
for (int root = 0; root < n; root++) {
long maxPath = maxPathFrom(root, -1, g, price);
ans = Math.max(ans, maxPath - price[root]);
}
return ans;
}
private long maxPathFrom(int u, int p, List<Integer>[] g, int[] price) {
long best = price[u];
for (int v : g[u]) {
if (v == p) continue;
best = Math.max(best, maxPathFrom(v, u, g, price) + price[u]);
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.maxOutput(6, new int[][]{{0,1},{1,2},{1,3},{3,4},{3,5}}, new int[]{9,8,7,6,10,5}) == 24 ? "PASS" : "FAIL");
System.out.println(s.maxOutput(3, new int[][]{{0,1},{1,2}}, new int[]{1,1,1}) == 2 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(9);
boolean ok = true;
for (int t = 0; t < 800; 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[] price = new int[n];
for (int i = 0; i < n; i++) price[i] = 1 + rnd.nextInt(9);
long r1 = s.maxOutput(n, edges, price);
long r2 = s.maxOutputBrute(n, edges, price);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch n=" + n + " edges=" + java.util.Arrays.deepToString(edges)
+ " price=" + java.util.Arrays.toString(price) + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2538 全部 PASS" : "2538 存在不一致");
}
}
复杂度分析
时间复杂度:O(n);空间复杂度:O(n)。
2539. Count the Number of Good Subsequences(好子序列的个数)
题目描述
如果字符串的某个子序列不为空,且其中每一个字符出现的频率都相同,
就认为该子序列是一个好子序列。给定字符串 s,统计并返回它的好子序列的个数,
答案对 10^9 + 7 取余。
示例 1
s = "aabb" -> 11
示例 2
s = "leet" -> 12
示例 3
s = "abcd" -> 15
提示:
- 1 <=
s.length<= 10^4;s仅由小写英文字母组成
解题思路
解法一:组合计数(主提交版本)
统计每个字符的出现次数 cntc。一个好子序列中,每个被选中的字符都恰好出现 f 次
(f >= 1),未选中的字符出现 0 次。因此固定 f 时,选择方案数为
Π_c ( 1 + C(cntc, f) )
其中 cntc < f 时 C(cntc, f) = 0(该字符只能不选)。减去全都不选的 1 种。
总和 = Σ_{f=1}^{maxCnt} ( Π_c (1 + C(cntc, f)) - 1 )。
组合数用阶乘 + 逆元 O(1) 查询。
解法二:二进制枚举,对照
穷举 s 的所有子序列(下标子集),检查是否为好子序列,
仅用于小数据对拍。
时间复杂度:O(26 * maxCnt);空间复杂度:O(maxCnt)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int countSubsequences(String s) {
int[] cnt = new int[26];
for (char ch : s.toCharArray()) cnt[ch - 'a']++;
int maxCnt = 0;
for (int c : cnt) maxCnt = Math.max(maxCnt, c);
if (maxCnt == 0) return 0;
// 预处理阶乘与逆元
long[] fact = new long[maxCnt + 1];
long[] invFact = new long[maxCnt + 1];
fact[0] = 1;
for (int i = 1; i <= maxCnt; i++) fact[i] = fact[i - 1] * i % MOD;
invFact[maxCnt] = modPow(fact[maxCnt], MOD - 2);
for (int i = maxCnt; i > 0; i--) invFact[i - 1] = invFact[i] * i % MOD;
long ans = 0;
for (int f = 1; f <= maxCnt; f++) {
long ways = 1;
for (int c = 0; c < 26; c++) {
if (cnt[c] >= f) {
long comb = fact[cnt[c]] * invFact[f] % MOD * invFact[cnt[c] - f] % MOD;
ways = ways * (1 + comb) % MOD;
}
}
ans = (ans + ways - 1) % MOD;
}
return (int) ((ans + MOD) % MOD);
}
private long modPow(long a, long e) {
long r = 1;
while (e > 0) {
if ((e & 1) == 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
public int countSubsequencesBrute(String s) {
int n = s.length();
int[] cnt = new int[26];
int good = 0;
for (int mask = 1; mask < (1 << n); mask++) {
java.util.Arrays.fill(cnt, 0);
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) cnt[s.charAt(i) - 'a']++;
}
int f = -1;
boolean ok = true;
for (int c = 0; c < 26; c++) {
if (cnt[c] > 0) {
if (f == -1) f = cnt[c];
else if (cnt[c] != f) { ok = false; break; }
}
}
if (ok) good++;
}
return good;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.countSubsequences("aabb") == 11 ? "PASS" : "FAIL");
System.out.println(s.countSubsequences("leet") == 12 ? "PASS" : "FAIL");
System.out.println(s.countSubsequences("abcd") == 15 ? "PASS" : "FAIL");
// 随机对拍(n <= 12)
java.util.Random rnd = new java.util.Random(10);
boolean ok = true;
for (int t = 0; t < 500; t++) {
StringBuilder sb = new StringBuilder();
int n = 1 + rnd.nextInt(10);
for (int i = 0; i < n; i++) sb.append((char) ('a' + rnd.nextInt(3)));
String str = sb.toString();
int r1 = s.countSubsequences(str);
int r2 = s.countSubsequencesBrute(str);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch s=" + str + " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2539 全部 PASS" : "2539 存在不一致");
}
}
复杂度分析
时间复杂度:O(26 * maxCnt);空间复杂度:O(maxCnt)。
2540. Minimum Common Value(最小公共值)
题目描述
给你两个已按非降序排序的整数数组 nums1 和 nums2,返回两个数组的最小公共整数。
如果两个数组没有公共整数,返回 -1。
如果一个整数在两个数组中都至少出现一次,那么这个整数是公共的。
示例 1
nums1 = [1,2,3], nums2 = [2,4] -> 2
示例 2
nums1 = [1,2,3,6], nums2 = [2,3,4,5] -> 2
提示:
- 1 <= nums1.length, nums2.length <= 10^5;1 <= nums1i, nums2j <= 10^9;
- 两个数组均非降序。
解题思路
解法一:双指针(主提交版本)
两指针从 0 开始,比较当前值,相等即答案;
小者指针后移。时间 O(n+m)。
解法二:二分查找,对照
遍历 nums1,对每个元素在 nums2 中二分查找。
时间 O(n log m)。
时间复杂度:O(n+m);空间复杂度:O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int getCommon(int[] nums1, int[] nums2) {
int i = 0, j = 0;
while (i < nums1.length && j < nums2.length) {
if (nums1[i] == nums2[j]) return nums1[i];
if (nums1[i] < nums2[j]) i++;
else j++;
}
return -1;
}
public int getCommonBinary(int[] nums1, int[] nums2) {
for (int x : nums1) {
int idx = Arrays.binarySearch(nums2, x);
if (idx >= 0) return x;
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例测试
System.out.println(s.getCommon(new int[]{1, 2, 3}, new int[]{2, 4}) == 2 ? "PASS" : "FAIL");
System.out.println(s.getCommon(new int[]{1, 2, 3, 6}, new int[]{2, 3, 4, 5}) == 2 ? "PASS" : "FAIL");
// 随机对拍
java.util.Random rnd = new java.util.Random(11);
boolean ok = true;
for (int t = 0; t < 2000; t++) {
int n = 1 + rnd.nextInt(15);
int m = 1 + rnd.nextInt(15);
int[] a = new int[n];
int[] b = new int[m];
for (int i = 0; i < n; i++) a[i] = rnd.nextInt(20);
for (int i = 0; i < m; i++) b[i] = rnd.nextInt(20);
Arrays.sort(a);
Arrays.sort(b);
int r1 = s.getCommon(a, b);
int r2 = s.getCommonBinary(a, b);
if (r1 != r2) {
ok = false;
System.out.println("Mismatch a=" + Arrays.toString(a) + " b=" + Arrays.toString(b)
+ " main=" + r1 + " brute=" + r2);
break;
}
}
System.out.println(ok ? "2540 全部 PASS" : "2540 存在不一致");
}
}
复杂度分析
小者指针后移。时间 O(n+m)。
时间 O(n log m)。
时间复杂度:O(n+m);空间复杂度:O(1)。