LeetCode 2821-2840. 题解合集
文章目录
- [LeetCode 2821-2840. 题解合集](#LeetCode 2821-2840. 题解合集)
-
- [2821. Delay the Resolution of Each Promise(延迟每个 Promise 对象的解析)](#2821. Delay the Resolution of Each Promise(延迟每个 Promise 对象的解析))
- [2822. Inversion of Object(对象反转)](#2822. Inversion of Object(对象反转))
- [2823. Deep Object Filter(深度对象筛选)](#2823. Deep Object Filter(深度对象筛选))
- [2824. Count Pairs Whose Sum is Less than Target(统计和小于目标的下标对数目)](#2824. Count Pairs Whose Sum is Less than Target(统计和小于目标的下标对数目))
- [2825. Make String a Subsequence Using Cyclic Increments(循环增长使字符串子序列等于另一个字符串)](#2825. Make String a Subsequence Using Cyclic Increments(循环增长使字符串子序列等于另一个字符串))
- [2826. Sorting Three Groups(将三个组排序)](#2826. Sorting Three Groups(将三个组排序))
- [2827. Number of Beautiful Integers in the Range(范围中美丽整数的数目)](#2827. Number of Beautiful Integers in the Range(范围中美丽整数的数目))
- [2828. Check if a String Is an Acronym of Words(判别首字母缩略词)](#2828. Check if a String Is an Acronym of Words(判别首字母缩略词))
- [2829. Determine the Minimum Sum of a k-avoiding Array(k-avoiding 数组的最小总和)](#2829. Determine the Minimum Sum of a k-avoiding Array(k-avoiding 数组的最小总和))
- [2830. Maximize the Profit as the Salesman(销售利润最大化)](#2830. Maximize the Profit as the Salesman(销售利润最大化))
- [2831. Find the Longest Equal Subarray(找出最长等值子数组)](#2831. Find the Longest Equal Subarray(找出最长等值子数组))
- [2832. Maximal Range That Each Element Is Maximum in It(每个元素为最大值的最大范围)](#2832. Maximal Range That Each Element Is Maximum in It(每个元素为最大值的最大范围))
- [2833. Furthest Point From Origin(距离原点最远的点)](#2833. Furthest Point From Origin(距离原点最远的点))
- [2834. Find the Minimum Possible Sum of a Beautiful Array(找出美丽数组的最小和)](#2834. Find the Minimum Possible Sum of a Beautiful Array(找出美丽数组的最小和))
- [2835. Minimum Operations to Form Subsequence With Target Sum(使子序列的和等于目标的最少操作次数)](#2835. Minimum Operations to Form Subsequence With Target Sum(使子序列的和等于目标的最少操作次数))
- [2836. Maximize Value of Function in a Ball Passing Game(在传球游戏中最大化函数值)](#2836. Maximize Value of Function in a Ball Passing Game(在传球游戏中最大化函数值))
- [2837. Total Traveled Distance(总旅行距离)](#2837. Total Traveled Distance(总旅行距离))
- [2838. Maximum Coins Heroes Can Collect(英雄可以获得的最大金币数)](#2838. Maximum Coins Heroes Can Collect(英雄可以获得的最大金币数))
- [2839. Check if Strings Can be Made Equal With Operations I(判断通过操作能否让字符串相等 I)](#2839. Check if Strings Can be Made Equal With Operations I(判断通过操作能否让字符串相等 I))
- [2840. Check if Strings Can be Made Equal With Operations II(判断通过操作能否让字符串相等 II)](#2840. Check if Strings Can be Made Equal With Operations II(判断通过操作能否让字符串相等 II))
2821. Delay the Resolution of Each Promise(延迟每个 Promise 对象的解析)
题目类型:JavaScript
题目描述
给定一个函数数组 functions 和一个数字 ms,返回一个新的函数数组。
- `functions` 是一个返回 `Promise` 对象的函数数组。
- `ms` 表示延迟的时间,以毫秒为单位。它决定了在新数组中的每个函数返回的 `Promise` 在解析之前等待的时间。
新数组中的每个函数应该返回一个 Promise 对象,在延迟了 ms 毫秒后解析,保持原始 functions 数组中的顺序。delayAll 函数应确保从 functions 中的每个 Promise 都被延迟执行,形成返回延迟的 Promise 的函数的新数组。
示例 1:**
输入:
functions = [
() => new Promise((resolve) => setTimeout(resolve, 30))
],
ms = 50
输出:[80]
解释:数组中的 Promise 在 30 毫秒后解析,但被延迟了 50 毫秒,所以总共延迟了 30 毫秒 + 50 毫秒 = 80 毫秒。
示例 2:**
输入:
functions = [
() => new Promise((resolve) => setTimeout(resolve, 50)),
() => new Promise((resolve) => setTimeout(resolve, 80))
],
ms = 70
输出:[120,150]
解释:数组中的 Promise 在 50 毫秒和 80 毫秒后解析,但它们被延迟了 70 毫秒,所以总共延迟了 50 毫秒 + 70 毫秒 = 120 毫秒 和 80 毫秒 + 70 毫秒 = 150 毫秒。
提示:
- `functions` 是一个返回 `Promise` 对象的函数数组
- `10 <= ms <= 500`
- `1 <= functions.length <= 10`
解题思路
解法一:async/await 包装(主提交版本)
- 对
functions中的每个函数,返回一个async () => { await sleep(ms); return await fn(); }; sleep用new Promise(r => setTimeout(r, ms))实现;- 保持原始顺序,每个 Promise 都在额外延迟
ms毫秒后解析。
代码实现
javascript
const delayAll = (functions, ms) =>
functions.map(
(fn) =>
async function () {
await new Promise((resolve) => setTimeout(resolve, ms));
return await fn();
},
);
复杂度分析
- 时间复杂度 :
O(n),n为输入规模,单次遍历(或递归)。 - 空间复杂度 :
O(n),需要存储中间结果/计数器。
2822. Inversion of Object(对象反转)
题目类型:JavaScript
题目描述
给定一个对象 obj,返回一个反转的对象 invertedObj。
invertedObj 应该以 obj 的键作为值,以 obj 的值作为键。题目保证 obj 中的值仅为字符串。该函数应该处理重复值,也就是说,如果在 obj 中有多个具有相同值的键,那么 invertedObj 应该将该值映射到一个包含所有相应键的数组中。
示例 1:**
输入:obj = {"a": "1", "b": "2", "c": "3", "d": "4"}
输出:invertedObj = {"1": "a", "2": "b", "3": "c", "4": "d"}
解释:obj 中的键变成 invertedObj 中的值,而 obj 中的值变成 invertedObj 中的键。
示例 2:**
输入:obj = {"a": "1", "b": "2", "c": "2", "d": "4"}
输出:invertedObj = {"1": "a", "2": ["b", "c"], "4": "d"}
解释:在 obj 中有两个具有相同值的键,invertedObj 将该值映射到一个包含所有对应键的数组中。
示例 3:**
输入:obj = ["1", "2", "3", "4"]
输出:invertedObj = {"1": "0", "2": "1", "3": "2", "4": "3"}
解释:数组也是对象,因此数组已经转换为一个对象,obj 中的键(索引)变成了 invertedObj 中的值,而 obj 中的值变成了 invertedObj 中的键。
提示:
- `obj` 是一个有效的 `JSON` 对象
- `typeof obj[key] === "string"`
- `2 <= JSON.stringify(obj).length <= 10**5`
解题思路
解法一:遍历键值反转(主提交版本)
- 遍历
obj的每个键值对,以值为键、原键为值构造invertedObj; - 若值已出现过(重复值),把该键追加到对应数组中:
invertedObj[val] = [...(invertedObj[val] ?? []), key]; - 仅出现一次的值仍映射为单个字符串(非数组)。
代码实现
javascript
var invertObject = function (obj) {
const res = {};
for (const key in obj) {
const val = obj[key];
res[val] = res[val] === undefined ? key : [...(Array.isArray(res[val]) ? res[val] : [res[val]]), key];
}
return res;
};
复杂度分析
- 时间复杂度 :
O(n),n为输入规模,单次遍历(或递归)。 - 空间复杂度 :
O(n),需要存储中间结果/计数器。
2823. Deep Object Filter(深度对象筛选)
题目类型:JavaScript
题目描述
给定一个对象 obj 和一个函数 fn,返回一个经过筛选的对象 filteredObject。
函数 deepFilter 应该在对象 obj 上执行深度筛选操作。深度筛选操作应该移除筛选函数 fn 输出为 false 的属性,以及在键被移除后仍然存在的任何空对象或数组。
如果深度筛选操作导致对象或数组为空,没有剩余属性,deepFilter 应该返回 undefined,表示在 filteredObject 中没有有效数据。
示例 1:**
输入:
obj = [-5, -4, -3, -2, -1, 0, 1],
fn = (x) => x > 0
输出:[1]
解释:所有不大于 0 的值都被移除。
示例 2:**
输入:
obj = {"a": 1, "b": "2", "c": 3, "d": "4", "e": 5, "f": 6, "g": {"a": 1}},
fn = (x) => typeof x === "string"
输出:{"b":"2","d":"4"}
解释:所有值不是字符串的键都被移除。在筛选过程中移除键后,任何导致为空的对象也被移除。
示例 3:**
输入:
obj = [-1, [-1, -1, 5, -1, 10], -1, [-1], [-5]],
fn = (x) => x > 0
输出:[[5,10]]
解释:所有不大于 0 的值都被移除。在筛选过程中移除值后,任何导致为空的数组也被移除。
示例 4:**
输入:
obj = [[[[5]]]],
fn = (x) => Array.isArray(x)
输出:undefined
提示:
- `fn` 是一个返回布尔值的函数
- `obj` 是一个有效的 `JSON` 对象
- `2 <= JSON.stringify(obj).length <= 10**5`
解题思路
解法一:递归深度筛选(主提交版本)
- 对数组:逐元素递归调用
deepFilter,过滤掉返回undefined的元素; - 对对象:逐键递归调用,删除值为
undefined的属性; - 筛选完成后:空数组 / 空对象(无剩余键)返回
undefined; - 叶子值直接调用
fn判定保留与否。
代码实现
javascript
var deepFilter = function (obj, fn) {
if (Array.isArray(obj)) {
const res = [];
for (const v of obj) {
const r = deepFilter(v, fn);
if (r !== undefined) res.push(r);
}
return res.length ? res : undefined;
}
if (obj !== null && typeof obj === 'object') {
const res = {};
for (const k in obj) {
const r = deepFilter(obj[k], fn);
if (r !== undefined) res[k] = r;
}
return Object.keys(res).length ? res : undefined;
}
return fn(obj) ? obj : undefined;
};
复杂度分析
- 时间复杂度 :
O(n),n为输入规模,单次遍历(或递归)。 - 空间复杂度 :
O(n),需要存储中间结果/计数器。
2824. Count Pairs Whose Sum is Less than Target(统计和小于目标的下标对数目)
题目描述
给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target ,
请你返回满足 0 <= i < j < n 且 nums[i] + nums[j] < target 的下标对 (i, j) 的数目。
示例 1
- 输入 :
nums= -1,1,2,3,1,target= 2 - 输出:3
- 解释:满足条件的是 (0,1)、(0,2)、(0,4),共 3 对。
示例 2
- 输入 :
nums= -6,2,5,-2,-7,-1,3,target= -2 - 输出 :10
提示: 1 <= nums.length == n <= 50-50 <= nums[i], target <= 50
解题思路
解法一:排序 + 双指针
nums 本身下标顺序无关紧要(只要求 i<j 的"对"数),排序后对每个 i,
找最大的 j 使 numsi+numsj < target,则 i 的对数为 j-i(去重)。
双指针从两端逼近:i 从左、j 从右;若 numsi+numsj < target,则贡献 j-i 个对,i 前进;
否则 j 后退。
时间复杂度 O(n log n),空间复杂度 O(log n)(排序栈)。
解法二:两层暴力
直接枚举所有 i<j 检查条件。时间复杂度 O(n^2),空间复杂度 O(1),仅用于对拍。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:排序 + 双指针(主提交版本) ----------
public int countPairs(List<Integer> nums, int target) {
int[] a = new int[nums.size()];
for (int i = 0; i < a.length; i++) a[i] = nums.get(i);
Arrays.sort(a);
int ans = 0;
int i = 0, j = a.length - 1;
while (i < j) {
if (a[i] + a[j] < target) {
ans += j - i;
i++;
} else {
j--;
}
}
return ans;
}
// ---------- 解法二:暴力(对照) ----------
public int countPairsBrute(List<Integer> nums, int target) {
int n = nums.size();
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums.get(i) + nums.get(j) < target) ans++;
}
}
return ans;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.countPairs(Arrays.asList(-1, 1, 2, 3, 1), 2) == 3, "2824 例1");
check(s.countPairs(Arrays.asList(-6, 2, 5, -2, -7, -1, 3), -2) == 10, "2824 例2");
java.util.Random rnd = new java.util.Random(42);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(20);
List<Integer> a = new ArrayList<>();
for (int i = 0; i < n; i++) a.add(-50 + rnd.nextInt(101));
int target = -50 + rnd.nextInt(101);
int m1 = s.countPairs(a, target);
int m2 = s.countPairsBrute(a, target);
if (m1 != m2) {
allOk = false;
System.out.println("对拍失败 a=" + a + " target=" + target + " 双指针=" + m1 + " 暴力=" + m2);
break;
}
}
check(allOk, "2824 随机对拍");
System.out.println("2824 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n log n),空间复杂度 O(log n)(排序栈)。
直接枚举所有 i<j 检查条件。时间复杂度 O(n^2),空间复杂度 O(1),仅用于对拍。
2825. Make String a Subsequence Using Cyclic Increments(循环增长使字符串子序列等于另一个字符串)
题目描述
给你一个下标从 0 开始的字符串 str1 和 str2 。
一次操作中,你选择 str1 中的若干下标。对于选中的每一个下标 i ,你将 str1[i] 循环递增,变成下一个字符。
也就是说 'a' 变成 'b' ,'b' 变成 'c' ,以此类推,'z' 变成 'a' 。
如果执行以上操作至多一次,可以让 str2 成为 str1 的子序列,请你返回 true ,否则返回 false 。
注意:一个字符串的子序列指的是从原字符串中删除一些字符后,剩下字符按照原本先后顺序组成的新字符串。
示例 1
- 输入 :
str1= "abc",str2= "ad" - 输出 :
true - 解释 :选择
str1中的下标 2 ,将str1[2]循环递增得到 'd' ,str1变成 "abd" 且str2是子序列。
示例 2
- 输入 :
str1= "zc",str2= "ad" - 输出 :
true
示例 3
- 输入 :
str1= "ab",str2= "d" - 输出 :
false
提示: 1 <= str1.length <= 10^51 <= str2.length <= 10^5str1和str2只包含小写英文字母。
解题思路
解法一:双指针贪心
用指针 p1 扫 str1、p2 扫 str2。对 str2p2 的目标字符 c,在 str1 中找到第一个
满足「等于 c 或 c-1(循环前一个)」的字符,因为每个字符最多被循环递增一次
(只变下一字符,不能再变回)。若找到则 p1、p2 各前进;否则返回 false。
时间复杂度 O(|str1|+|str2|),空间复杂度 O(1)。
解法二:BFS/回溯模拟
枚举所有「选哪些下标递增」的组合(2^n 种)检查子序列,仅适用于极短字符串,用递归穷举对照。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Solution {
// ---------- 解法一:双指针贪心(主提交版本) ----------
public boolean canMakeSubsequence(String str1, String str2) {
int p1 = 0, p2 = 0;
int n = str1.length();
while (p2 < str2.length()) {
// 找 str1 中第一个能匹配 str2[p2] 的位置
while (p1 < n && !match(str1.charAt(p1), str2.charAt(p2))) {
p1++;
}
if (p1 == n) return false;
p1++;
p2++;
}
return true;
}
// c1 经过至多一次循环递增能否变成 c2(即 c1 == c2 或 c1+1 == c2,z 后回到 a)
private boolean match(char c1, char c2) {
if (c1 == c2) return true;
char next = (char) ((c1 - 'a' + 1) % 26 + 'a');
return next == c2;
}
// ---------- 解法二:递归穷举(小规模对照) ----------
public boolean canMakeSubsequenceBrute(String str1, String str2) {
return dfs(str1, str2, 0, 0);
}
// 从 str1 的 pos 位置开始,尝试匹配 str2 的 idx 位置
private boolean dfs(String str1, String str2, int pos, int idx) {
if (idx == str2.length()) return true;
if (pos == str1.length()) return false;
// 不选 str1[pos]
if (dfs(str1, str2, pos + 1, idx)) return true;
// 选 str1[pos](原始或递增后)匹配 str2[idx]
if (match(str1.charAt(pos), str2.charAt(idx)) && dfs(str1, str2, pos + 1, idx + 1)) return true;
return false;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.canMakeSubsequence("abc", "ad"), "2825 例1 true");
check(s.canMakeSubsequence("zc", "ad"), "2825 例2 true");
check(!s.canMakeSubsequence("ab", "d"), "2825 例3 false");
Random rnd = new Random(43);
boolean allOk = true;
for (int t = 0; t < 300; t++) {
int n1 = 1 + rnd.nextInt(8);
int n2 = 1 + rnd.nextInt(8);
StringBuilder sb1 = new StringBuilder(), sb2 = new StringBuilder();
for (int i = 0; i < n1; i++) sb1.append((char) ('a' + rnd.nextInt(26)));
for (int i = 0; i < n2; i++) sb2.append((char) ('a' + rnd.nextInt(26)));
String a = sb1.toString(), b = sb2.toString();
boolean x = s.canMakeSubsequence(a, b);
boolean y = s.canMakeSubsequenceBrute(a, b);
if (x != y) {
allOk = false;
System.out.println("对拍失败 str1=" + a + " str2=" + b + " 贪心=" + x + " 穷举=" + y);
break;
}
}
check(allOk, "2825 随机对拍");
System.out.println("2825 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(|str1|+|str2|),空间复杂度 O(1)。
2826. Sorting Three Groups(将三个组排序)
题目描述
给你一个整数数组 nums 。nums 的每个元素是 1,2 或 3。在每次操作中,你可以删除 nums 中的一个元素。
返回使 nums 成为非递减顺序所需操作数的最小值。
示例 1
- 输入 :
nums= 2,1,3,2,1 - 输出:3
- 解释 :删除
nums[0],nums[2]和nums[3]后得到 1,2,1。
示例 2
- 输入 :
nums= 1,3,2,1,3,3 - 输出:2
- 解释 :删除
nums[1]和nums[2]后得到 1,1,3,3。
示例 3
- 输入 :
nums= 2,2,2,2,3,3 - 输出 :0
提示: 1 <= nums.length <= 1001 <= nums[i] <= 3
解题思路
解法一:LIS 最长非递减子序列
删除最少的元素使剩余序列非递减 = n - 最长非递减子序列长度(LNDS)。
标准 DP:dpi = 以 i 结尾的最长非递减长度,dpi = max(dpj) + 1 (j<i 且 aj<=ai)。
时间复杂度 O(n^2),空间复杂度 O(n)。
进阶 O(n log n):因为值域只有 3,可退化为「前缀选择枚举」,见解法三。
解法二:暴力枚举删除子集
枚举所有保留子集检查非递减,仅限 n <= 15 的小规模对拍。
解法三:枚举分组边界 O(n)
最终序列形如 1...1 2...2 3...3(可缺省某些组)。枚举两个边界位置,
统计「原数组与最终形态不同的元素数」即需删除数。O(n + 9) 常数。作为第三验证。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:LNDS(主提交版本) ----------
public int minimumOperations(List<Integer> nums) {
int n = nums.size();
int[] dp = new int[n];
int best = 0;
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums.get(j) <= nums.get(i)) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
best = Math.max(best, dp[i]);
}
return n - best;
}
// ---------- 解法二:枚举删除子集(小规模对照) ----------
public int minimumOperationsBrute(List<Integer> nums) {
int n = nums.size();
int best = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int cnt = Integer.bitCount(mask);
if (cnt <= best) continue;
boolean ok = true;
int prev = 0, first = -1;
for (int i = 0; i < n; i++) {
if (((mask >> i) & 1) != 0) {
int v = nums.get(i);
if (first != -1 && v < prev) { ok = false; break; }
prev = v;
first = 1;
}
}
if (ok) best = cnt;
}
return n - best;
}
// ---------- 解法三:枚举边界 O(n)(对照) ----------
public int minimumOperationsEnum(List<Integer> nums) {
int n = nums.size();
int res = n;
// 最终形态:前 a 个是 1,中间 b 个是 2,后 n-a-b 个是 3
for (int a = 0; a <= n; a++) {
for (int b = 0; a + b <= n; b++) {
int changes = 0;
for (int i = 0; i < n; i++) {
int want = (i < a) ? 1 : (i < a + b ? 2 : 3);
if (nums.get(i) != want) changes++;
}
res = Math.min(res, changes);
}
}
return res;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.minimumOperations(Arrays.asList(2, 1, 3, 2, 1)) == 3, "2826 例1");
check(s.minimumOperations(Arrays.asList(1, 3, 2, 1, 3, 3)) == 2, "2826 例2");
check(s.minimumOperations(Arrays.asList(2, 2, 2, 2, 3, 3)) == 0, "2826 例3");
check(s.minimumOperations(Arrays.asList(2, 2, 1, 1)) == 2, "2826 附加");
java.util.Random rnd = new java.util.Random(44);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(13);
List<Integer> a = new ArrayList<>();
for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(3));
int m1 = s.minimumOperations(a);
int m2 = s.minimumOperationsBrute(a);
int m3 = s.minimumOperationsEnum(a);
if (m1 != m2 || m1 != m3) {
allOk = false;
System.out.println("对拍失败 a=" + a + " LNDS=" + m1 + " 暴力=" + m2 + " 边界=" + m3);
break;
}
}
check(allOk, "2826 三方对拍");
System.out.println("2826 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n^2),空间复杂度 O(n)。
2827. Number of Beautiful Integers in the Range(范围中美丽整数的数目)
题目描述
给你正整数 low ,high 和 k 。
如果一个数满足以下两个条件,那么它是美丽的:
- 偶数数位的数目与奇数数位的数目相同。
- 这个整数可以被
k整除。
请你返回范围 `low`, `high` 中美丽整数的数目。
示例 1
- 输入 :
low= 10,high= 20,k= 3 - 输出:2
- 解释:范围中有 2 个美丽数字:12,18。
示例 2
- 输入 :
low= 1,high= 10,k= 1 - 输出:1
- 解释:给定范围中有 1 个美丽数字:10。
示例 3
- 输入 :
low= 5,high= 5,k= 2 - 输出 :0
提示: 1 <= low <= high <= 10^91 <= k <= 20
解题思路
解法一:数位 DP
统计 1, x 内美丽整数个数 f(x),答案 = f(high) - f(low - 1)。
f(x) 用数位 DP:dfs(pos, rem, diff, tight, started),其中
pos 当前位、rem 是当前数对 k 的余数、diff = 偶数位个数 - 奇数位个数(转偏移量)、
tight 是否贴上界、started 是否已开始(处理前导零)。
转移:每位填 d,rem' = (rem*10 + d) % k,diff' = diff + (d 为偶数 ? +1 : -1)。
结束时判断 rem == 0 且 diff == 0(偏移归零)。
注意 diff 偏移范围 -10...10(最多 10 位),用 diff + 11 作下标。
时间复杂度 O(位数 * k * 偏移),空间复杂度同。
解法二:暴力枚举
直接遍历 low, high 每个数检查两个条件(用于小范围对拍)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:数位 DP(主提交版本) ----------
public int numberOfBeautifulIntegers(int low, int high, int k) {
return f(high, k) - f(low - 1, k);
}
private int f(int x, int k) {
if (x <= 0) return 0;
char[] s = String.valueOf(x).toCharArray();
int n = s.length;
// memo[pos][rem][off] off = diff + 11 (0..22),仅非贴上下界时缓存
Integer[][][] memo = new Integer[n][k][23];
return dfs(s, 0, 0, 0, true, false, k, memo);
}
private int dfs(char[] s, int pos, int rem, int off, boolean tight, boolean started, int k,
Integer[][][] memo) {
if (pos == s.length) {
return (started && rem == 0 && off == 0) ? 1 : 0;
}
if (!tight && started && memo[pos][rem][off + 11] != null) {
return memo[pos][rem][off + 11];
}
int limit = tight ? (s[pos] - '0') : 9;
int cnt = 0;
for (int d = 0; d <= limit; d++) {
boolean nt = tight && (d == limit);
if (!started) {
if (d == 0) {
cnt += dfs(s, pos + 1, rem, off, nt, false, k, memo);
} else {
int nrem = (rem * 10 + d) % k;
int noff = off + (d % 2 == 0 ? 1 : -1);
cnt += dfs(s, pos + 1, nrem, noff, nt, true, k, memo);
}
} else {
int nrem = (rem * 10 + d) % k;
int noff = off + (d % 2 == 0 ? 1 : -1);
cnt += dfs(s, pos + 1, nrem, noff, nt, true, k, memo);
}
}
if (!tight && started) {
memo[pos][rem][off + 11] = cnt;
}
return cnt;
}
// ---------- 解法二:暴力(对照) ----------
public int numberOfBeautifulIntegersBrute(int low, int high, int k) {
int ans = 0;
for (int v = low; v <= high; v++) {
int even = 0, odd = 0, tmp = v;
while (tmp > 0) {
int d = tmp % 10;
if (d % 2 == 0) even++;
else odd++;
tmp /= 10;
}
if (even == odd && v % k == 0) ans++;
}
return ans;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.numberOfBeautifulIntegers(10, 20, 3) == 2, "2827 例1");
check(s.numberOfBeautifulIntegers(1, 10, 1) == 1, "2827 例2");
check(s.numberOfBeautifulIntegers(5, 5, 2) == 0, "2827 例3");
java.util.Random rnd = new java.util.Random(45);
boolean allOk = true;
for (int t = 0; t < 300; t++) {
int hi = 1 + rnd.nextInt(20000);
int lo = 1 + rnd.nextInt(hi);
int k = 1 + rnd.nextInt(20);
int a = s.numberOfBeautifulIntegers(lo, hi, k);
int b = s.numberOfBeautifulIntegersBrute(lo, hi, k);
if (a != b) {
allOk = false;
System.out.println("对拍失败 lo=" + lo + " hi=" + hi + " k=" + k + " dp=" + a + " 暴力=" + b);
break;
}
}
check(allOk, "2827 随机对拍");
System.out.println("2827 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(位数 * k * 偏移),空间复杂度同。
2828. Check if a String Is an Acronym of Words(判别首字母缩略词)
题目描述
给你一个字符串数组 words 和一个字符串 s ,请你判断 s 是不是 words 的首字母缩略词。
如果可以按顺序串联 words 中每个字符串的第一个字符形成字符串 s ,则认为 s 是 words 的首字母缩略词。
例如,"ab" 可以由 "`apple`", "`banana`" 形成,但是无法从 "`bear`", "`aardvark`" 形成。
如果 s 是 words 的首字母缩略词,返回 true ;否则,返回 false 。
示例 1
- 输入 :
words= "`alice`","`bob`","`charlie`",s= "abc" - 输出 :
true
示例 2
- 输入 :
words= "an","`apple`",s= "a" - 输出 :
false
示例 3
- 输入 :
words= "`never`","`gonna`","`give`","`up`","on","`you`",s= "ngguoy" - 输出 :
true
提示: 1 <= words.length <= 1001 <= words[i].length <= 101 <= s.length <= 100words[i]和s由小写英文字母组成
解题思路
解法一:拼缩写后比较
遍历 words,把每个单词首字母拼成 StringBuilder,最后与 s 比较。
时间复杂度 O(总字符数),空间复杂度 O(n)。
解法二:逐字符比对
直接遍历校验 s 的第 i 个字符是否等于 wordsi 的首字符,同时检查长度一致。
时间复杂度 O(n),空间复杂度 O(1)。作为对照。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:拼缩写比较(主提交版本) ----------
public boolean isAcronym(List<String> words, String s) {
if (words.size() != s.length()) return false;
for (int i = 0; i < words.size(); i++) {
if (words.get(i).charAt(0) != s.charAt(i)) return false;
}
return true;
}
// ---------- 解法二:拼接再比较(对照) ----------
public boolean isAcronymBrute(List<String> words, String s) {
StringBuilder sb = new StringBuilder();
for (String w : words) sb.append(w.charAt(0));
return sb.toString().equals(s);
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.isAcronym(Arrays.asList("alice", "bob", "charlie"), "abc"), "2828 例1 true");
check(!s.isAcronym(Arrays.asList("an", "apple"), "a"), "2828 例2 false");
check(s.isAcronym(Arrays.asList("never", "gonna", "give", "up", "on", "you"), "ngguoy"), "2828 例3 true");
java.util.Random rnd = new java.util.Random(46);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(10);
List<String> words = new ArrayList<>();
StringBuilder acro = new StringBuilder();
for (int i = 0; i < n; i++) {
int len = 1 + rnd.nextInt(4);
StringBuilder w = new StringBuilder();
for (int j = 0; j < len; j++) w.append((char) ('a' + rnd.nextInt(26)));
words.add(w.toString());
acro.append(w.charAt(0));
}
String s1 = acro.toString();
String s2 = s1 + (char) ('a' + rnd.nextInt(26)); // 可能多一个字符
if (rnd.nextBoolean()) s2 = s2.substring(0, s2.length() - 1);
else if (s2.length() > 1 && rnd.nextBoolean()) {
// 随机改一个字符
char[] c = s2.toCharArray();
c[rnd.nextInt(c.length)] = (char) ('a' + rnd.nextInt(26));
s2 = new String(c);
}
boolean a = s.isAcronym(words, s1);
boolean b = s.isAcronymBrute(words, s1);
boolean c1 = s.isAcronym(words, s2);
boolean c2 = s.isAcronymBrute(words, s2);
if (a != b || c1 != c2) {
allOk = false;
System.out.println("对拍失败 words=" + words + " 原=" + a + "/" + b);
break;
}
}
check(allOk, "2828 随机对拍");
System.out.println("2828 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(总字符数),空间复杂度 O(n)。
时间复杂度 O(n),空间复杂度 O(1)。作为对照。
2829. Determine the Minimum Sum of a k-avoiding Array(k-avoiding 数组的最小总和)
题目描述
给你两个整数 n 和 k 。
对于一个由不同正整数组成的数组,如果其中不存在任何求和等于 k 的不同元素对,则称其为 k-avoiding 数组。
返回长度为 n 的 k-avoiding 数组的可能的最小总和。
示例 1
- 输入 :
n= 5,k= 4 - 输出:18
- 解释 :设若
k-avoiding数组为 1,2,4,5,6 ,其元素总和为 18 。
示例 2
- 输入 :
n= 2,k= 6 - 输出:3
- 解释 :可以构造数组 1,2 ,其元素总和为 3 。
提示: 1 <= n, k <= 50
解题思路
解法一:贪心取最小合法数
从小到大逐个尝试正整数,若 x 与已选集合中任意元素的和 == k 则跳过,否则选入,直到选满 n 个。
为什么贪心正确:最小和的 k-avoiding 数组一定是字典序最小的一组互不冲突正整数(交换论证)。
时间复杂度 O(n*k),空间复杂度 O(n)。
解法二:枚举所有组合
用 DFS/回溯枚举长度为 n 的互异正整数组合,检查是否 k-avoiding,取最小和(候选值上界
设为 k+n 足够,因为最多选 n 个,第 n 个 <= k+n-1 必然可行)。用于小规模对拍。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
// ---------- 解法一:贪心(主提交版本) ----------
public int minimumSum(int n, int k) {
boolean[] used = new boolean[2 * k + 60];
int sum = 0;
int cnt = 0;
for (int x = 1; cnt < n; x++) {
if (k - x >= 1 && k - x < used.length && used[k - x] && k - x != x) {
continue; // 与已选元素和为 k
}
used[x] = true;
sum += x;
cnt++;
}
return sum;
}
// ---------- 解法二:回溯枚举(对照) ----------
private int bestSum;
private int nVal, kVal;
public int minimumSumBrute(int n, int k) {
nVal = n;
kVal = k;
bestSum = Integer.MAX_VALUE;
dfs(1, 0, 0, new boolean[200]);
return bestSum;
}
private void dfs(int start, int cnt, int sum, boolean[] used) {
if (cnt == nVal) {
bestSum = Math.min(bestSum, sum);
return;
}
if (sum >= bestSum) return;
for (int x = start; x <= 120; x++) {
// 检查是否与已选元素和为 k
boolean conflict = false;
for (int y = 1; y < x; y++) {
if (used[y] && y + x == kVal) { conflict = true; break; }
}
if (conflict) continue;
used[x] = true;
dfs(x + 1, cnt + 1, sum + x, used);
used[x] = false;
}
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.minimumSum(5, 4) == 18, "2829 例1");
check(s.minimumSum(2, 6) == 3, "2829 例2");
check(s.minimumSum(1, 1) == 1, "2829 附加1");
java.util.Random rnd = new java.util.Random(47);
boolean allOk = true;
for (int t = 0; t < 200; t++) {
int n = 1 + rnd.nextInt(6);
int k = 1 + rnd.nextInt(12);
int a = s.minimumSum(n, k);
int b = s.minimumSumBrute(n, k);
if (a != b) {
allOk = false;
System.out.println("对拍失败 n=" + n + " k=" + k + " 贪心=" + a + " 回溯=" + b);
break;
}
}
check(allOk, "2829 随机对拍");
System.out.println("2829 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n*k),空间复杂度 O(n)。
2830. Maximize the Profit as the Salesman(销售利润最大化)
题目描述
给你一个整数 n 表示数轴上的房屋数量,编号从 0 到 n - 1 。
另给你一个二维整数数组 offers ,其中 offers[i] = `starti`, `endi`, `goldi`
表示第 i 个买家想要以 goldi 枚金币的价格购买从 starti 到 endi 的所有房屋。
作为一名销售,你需要有策略地选择并销售房屋使自己的收入最大化。
返回你可以赚取的金币的最大数目。
注意:同一所房屋不能卖给不同的买家,并且允许保留一些房屋不进行出售。
示例 1
- 输入 :
n= 5,offers= \[0,0,1,0,2,2,1,3,2] - 输出:3
示例 2
- 输入 :
n= 5,offers= \[0,0,1,0,2,10,1,3,2] - 输出 :10
提示: 1 <= n <= 10^51 <= offers.length <= 10^5offers[i].length == 30 <= starti <= endi <= n - 11 <= goldi <= 10^3
解题思路
解法一:DP + 按结束位置分组
dpi = 只考虑前 i 间房屋(编号 0...i-1)的最大收益。
转移:dpi+1 = max(dpi, dpi+1); 对每个以 i 结尾的 offer s, i, g,
dpi+1 = max(dpi+1, dps + g)(s 之前房屋与 s...i 不重叠)。
先把 offers 按 endi 分组,再顺序 DP。
时间复杂度 O(n + offers.length),空间复杂度 O(n + offers.length)。
解法二:暴力 DFS 子集
枚举选中哪些 offer 集合,检查区间互不重叠,求和取最大(小规模对拍)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:DP(主提交版本) ----------
public int maximizeTheProfit(int n, List<List<Integer>> offers) {
List<int[]>[] byEnd = new List[n];
for (int i = 0; i < n; i++) byEnd[i] = new ArrayList<>();
for (List<Integer> o : offers) {
byEnd[o.get(1)].add(new int[]{o.get(0), o.get(2)});
}
int[] dp = new int[n + 1];
for (int i = 0; i < n; i++) {
dp[i + 1] = dp[i]; // 不卖以 i 结尾的任何 offer
for (int[] of : byEnd[i]) {
dp[i + 1] = Math.max(dp[i + 1], dp[of[0]] + of[1]);
}
}
return dp[n];
}
// ---------- 解法二:DFS 子集(对照) ----------
private int best;
public int maximizeTheProfitBrute(int n, List<List<Integer>> offers) {
int m = offers.size();
best = 0;
for (int mask = 0; mask < (1 << m); mask++) {
if (m > 20) break; // 防爆
int sum = 0;
boolean ok = true;
boolean[] occupied = new boolean[n];
for (int i = 0; i < m; i++) {
if (((mask >> i) & 1) != 0) {
int s = offers.get(i).get(0), e = offers.get(i).get(1);
for (int p = s; p <= e; p++) {
if (occupied[p]) { ok = false; break; }
occupied[p] = true;
}
if (!ok) break;
sum += offers.get(i).get(2);
}
}
if (ok) best = Math.max(best, sum);
}
return best;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.maximizeTheProfit(5, new ArrayList<>(Arrays.asList(
Arrays.asList(0, 0, 1), Arrays.asList(0, 2, 2), Arrays.asList(1, 3, 2)))) == 3, "2830 例1");
check(s.maximizeTheProfit(5, new ArrayList<>(Arrays.asList(
Arrays.asList(0, 0, 1), Arrays.asList(0, 2, 10), Arrays.asList(1, 3, 2)))) == 10, "2830 例2");
java.util.Random rnd = new java.util.Random(48);
boolean allOk = true;
for (int t = 0; t < 100; t++) {
int n = 3 + rnd.nextInt(6);
int m = 1 + rnd.nextInt(12);
List<List<Integer>> offers = new ArrayList<>();
for (int i = 0; i < m; i++) {
int st = rnd.nextInt(n);
int e = st + rnd.nextInt(n - st);
offers.add(Arrays.asList(st, e, 1 + rnd.nextInt(50)));
}
int a = s.maximizeTheProfit(n, offers);
int b = s.maximizeTheProfitBrute(n, offers);
if (a != b) {
allOk = false;
System.out.println("对拍失败 n=" + n + " offers=" + offers + " dp=" + a + " 暴力=" + b);
break;
}
}
check(allOk, "2830 随机对拍");
System.out.println("2830 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n + offers.length),空间复杂度 O(n + offers.length)。
2831. Find the Longest Equal Subarray(找出最长等值子数组)
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。
如果子数组中所有元素都相等,则认为子数组是一个等值子数组。注意,空数组是等值子数组。
从 nums 中删除最多 k 个元素后,返回可能的最长等值子数组的长度。
子数组是数组中一个连续且可能为空的元素序列。
示例 1
- 输入 :
nums= 1,3,2,3,1,3,k= 3 - 输出:3
- 解释 :删除下标 2 和 4 后
nums=1,3,3,3,最长等值子数组长度 3。
示例 2
- 输入 :
nums= 1,1,2,2,1,1,k= 2 - 输出 :4
提示: 1 <= nums.length <= 10^51 <= nums[i] <= nums.length0 <= k <= nums.length
解题思路
解法一:哈希表分组 + 滑动窗口
对每个值 v,记录其所有出现下标列表 posv。
对每个 v 的 pos 列表做滑动窗口:窗口 l...r 覆盖的下标区间跨度 = posr - posl + 1,
窗口内需要删除的元素数 = 跨度 - 窗口长度(即不是 v 的元素数)。
若需要删除数 > k 则 l 右移;窗口长度 r-l+1 即为该 v 的最长等值子数组长度。
时间复杂度 O(n),空间复杂度 O(n)。
解法二:暴力枚举 + 校验
枚举每个子数组的候选 v 与长度(小规模对拍),检查删除 <= k 后能否全等。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Solution {
// ---------- 解法一:分组 + 滑动窗口(主提交版本) ----------
public int longestEqualSubarray(List<Integer> nums, int k) {
int n = nums.size();
Map<Integer, List<Integer>> pos = new HashMap<>();
for (int i = 0; i < n; i++) {
pos.computeIfAbsent(nums.get(i), x -> new ArrayList<>()).add(i);
}
int ans = 0;
for (List<Integer> idx : pos.values()) {
int l = 0;
for (int r = 0; r < idx.size(); r++) {
// 跨度 = idx[r] - idx[l] + 1,窗口内 v 出现次数 = r-l+1
while (idx.get(r) - idx.get(l) + 1 - (r - l + 1) > k) {
l++;
}
ans = Math.max(ans, r - l + 1);
}
}
return ans;
}
// ---------- 解法二:暴力(对照) ----------
public int longestEqualSubarrayBrute(List<Integer> nums, int k) {
int n = nums.size();
int ans = 0;
Set<Integer> values = new HashSet<>();
for (int x : nums) values.add(x);
for (int v : values) {
// 预处理 v 的位置
List<Integer> idx = new ArrayList<>();
for (int i = 0; i < n; i++) if (nums.get(i) == v) idx.add(i);
for (int l = 0; l < idx.size(); l++) {
for (int r = l; r < idx.size(); r++) {
int span = idx.get(r) - idx.get(l) + 1;
int cnt = r - l + 1;
if (span - cnt <= k) ans = Math.max(ans, cnt);
}
}
}
return ans;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.longestEqualSubarray(Arrays.asList(1, 3, 2, 3, 1, 3), 3) == 3, "2831 例1");
check(s.longestEqualSubarray(Arrays.asList(1, 1, 2, 2, 1, 1), 2) == 4, "2831 例2");
java.util.Random rnd = new java.util.Random(49);
boolean allOk = true;
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(10);
List<Integer> a = new ArrayList<>();
for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(4));
int k = rnd.nextInt(n + 1);
int m1 = s.longestEqualSubarray(a, k);
int m2 = s.longestEqualSubarrayBrute(a, k);
if (m1 != m2) {
allOk = false;
System.out.println("对拍失败 a=" + a + " k=" + k + " 滑窗=" + m1 + " 暴力=" + m2);
break;
}
}
check(allOk, "2831 随机对拍");
System.out.println("2831 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n),空间复杂度 O(n)。
2832. Maximal Range That Each Element Is Maximum in It(每个元素为最大值的最大范围)
题目描述
现给定一个由不同整数构成的 0 索引数组 nums 。
我们用以下方式定义与 nums 长度相同的 0 索引数组 ans :
ans[i] 是子数组 nums`l`...`r` 的最大长度,该子数组中的最大元素等于 nums[i] 。
返回数组 ans 。
注意,子数组是数组的连续部分。
示例 1
- 输入 :
nums= 1,5,4,3,6 - 输出:1,4,2,1,5
- 解释 :对于
nums[0],最长的子数组,其中最大值为 1,是nums0...0,所以ans[0]= 1。
对于nums[1],最长的子数组是nums0...3,其中最大值为 5,所以ans[1]= 4。
对于nums[2],最长的子数组是nums2...3,其中最大值为 4,所以ans[2]= 2。
对于nums[3],最长的子数组是nums3...3,其中最大值为 3,所以ans[3]= 1。
对于nums[4],最长的子数组是nums0...4,其中最大值为 6,所以ans[4]= 5。
示例 2
- 输入 :
nums= 1,2,3,4,5 - 输出 :1,2,3,4,5
提示: 1 <= nums.length <= 10^51 <= nums[i] <= 10^9nums中的所有整数互不相同
解题思路
解法一:单调栈求左右边界
ansi 是「以 numsi 为最大值的最大子数组长度」= Ri - Li - 1,其中
Li = 左边第一个比 numsi 大的下标(开区间),Ri = 右边第一个比 numsi 大的下标。
用单调递减栈一次遍历求 L(从左往右),一次遍历求 R(从右往左)。
时间复杂度 O(n),空间复杂度 O(n)。
解法二:O(n^2) 向两边扩展
对每个 i,向左右找第一个比它大的元素,计算区间长度。仅用于小规模对拍。
代码实现
java
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
public class Solution {
// ---------- 解法一:单调栈(主提交版本) ----------
public int[] maximumLengthOfRanges(int[] nums) {
int n = nums.length;
int[] L = new int[n]; // 左边第一个更大的下标(无则 -1)
int[] R = new int[n]; // 右边第一个更大的下标(无则 n)
Deque<Integer> st = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
while (!st.isEmpty() && nums[st.peek()] < nums[i]) st.pop();
L[i] = st.isEmpty() ? -1 : st.peek();
st.push(i);
}
st.clear();
for (int i = n - 1; i >= 0; i--) {
while (!st.isEmpty() && nums[st.peek()] < nums[i]) st.pop();
R[i] = st.isEmpty() ? n : st.peek();
st.push(i);
}
int[] ans = new int[n];
for (int i = 0; i < n; i++) ans[i] = R[i] - L[i] - 1;
return ans;
}
// ---------- 解法二:向两边扩展(对照) ----------
public int[] maximumLengthOfRangesBrute(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
int l = i, r = i;
while (l - 1 >= 0 && nums[l - 1] < nums[i]) l--;
while (r + 1 < n && nums[r + 1] < nums[i]) r++;
ans[i] = r - l + 1;
}
return ans;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(Arrays.equals(s.maximumLengthOfRanges(new int[]{1, 5, 4, 3, 6}),
new int[]{1, 4, 2, 1, 5}), "2832 例1");
check(Arrays.equals(s.maximumLengthOfRanges(new int[]{1, 2, 3, 4, 5}),
new int[]{1, 2, 3, 4, 5}), "2832 例2");
java.util.Random rnd = new java.util.Random(50);
boolean allOk = true;
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(10);
int[] arr = new int[n];
java.util.Set<Integer> used = new java.util.HashSet<>();
for (int i = 0; i < n; i++) {
int v;
do { v = 1 + rnd.nextInt(60); } while (used.contains(v));
used.add(v);
arr[i] = v;
}
int[] a = s.maximumLengthOfRanges(arr);
int[] b = s.maximumLengthOfRangesBrute(arr);
if (!Arrays.equals(a, b)) {
allOk = false;
System.out.println("对拍失败 arr=" + Arrays.toString(arr) + " 栈=" + Arrays.toString(a)
+ " 扩展=" + Arrays.toString(b));
break;
}
}
check(allOk, "2832 随机对拍");
System.out.println("2832 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n),空间复杂度 O(n)。
2833. Furthest Point From Origin(距离原点最远的点)
题目描述
给你一个长度为 n 的字符串 moves ,该字符串仅由字符 'L'、'R' 和 '_' 组成。
字符串表示你在一条原点为 0 的数轴上的若干次移动。
你的初始位置就在原点(0),第 i 次移动过程中,你可以根据对应字符选择移动方向:
- 如果
moves[i]= 'L' 或moves[i]= '_' ,可以选择向左移动一个单位距离 - 如果
moves[i]= 'R' 或moves[i]= '_' ,可以选择向右移动一个单位距离
返回在移动n次之后,可以到达的距离原点最远的点到原点的距离。
示例 1
- 输入 :
moves= "L_RL__R" - 输出:3
- 解释 :可以到达的距离原点 0 最远的点是 -3 ,移动的序列为 "
LLRLLLR" 。
示例 2
- 输入 :
moves= "_R__LL_" - 输出:5
示例 3
- 输入 :
moves= "_______" - 输出 :7
提示: 1 <= moves.length == n <= 50moves只含有字符 'L'、'R' 和 '_'
解题思路
解法一:计数公式
所有 '' 可以统一选择方向。若把全部 ' ' 都往右走,终点 = (R 数 + '' 数) - L 数;
若全部往左走,终点 = R 数 - (L 数 + ' ' 数)。取两者绝对值的较大者。
时间复杂度 O(n),空间复杂度 O(1)。
解法二:枚举 '_' 方向
'' 数量有限(<=50),枚举每个 '' 选左或右的所有组合,计算最远距离。用于对拍。
代码实现
java
import java.util.Random;
public class Solution {
// ---------- 解法一:计数公式(主提交版本) ----------
public int furthestDistanceFromOrigin(String moves) {
int l = 0, r = 0, u = 0;
for (char c : moves.toCharArray()) {
if (c == 'L') l++;
else if (c == 'R') r++;
else u++;
}
return Math.max(Math.abs((r + u) - l), Math.abs(r - (l + u)));
}
// ---------- 解法二:枚举 '_' 方向(对照) ----------
public int furthestDistanceFromOriginBrute(String moves) {
int n = moves.length();
int cntU = 0;
for (char c : moves.toCharArray()) if (c == '_') cntU++;
int best = 0;
for (int mask = 0; mask < (1 << cntU); mask++) {
int pos = 0, u = 0;
for (char c : moves.toCharArray()) {
if (c == 'L') pos--;
else if (c == 'R') pos++;
else {
if (((mask >> u) & 1) != 0) pos++;
else pos--;
u++;
}
}
best = Math.max(best, Math.abs(pos));
}
return best;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.furthestDistanceFromOrigin("L_RL__R") == 3, "2833 例1");
check(s.furthestDistanceFromOrigin("_R__LL_") == 5, "2833 例2");
check(s.furthestDistanceFromOrigin("_______") == 7, "2833 例3");
Random rnd = new Random(51);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
int n = rnd.nextInt(12) + 1;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
int r = rnd.nextInt(3);
sb.append(r == 0 ? 'L' : (r == 1 ? 'R' : '_'));
}
String m = sb.toString();
int a = s.furthestDistanceFromOrigin(m);
int b = s.furthestDistanceFromOriginBrute(m);
if (a != b) {
allOk = false;
System.out.println("对拍失败 moves=" + m + " 公式=" + a + " 枚举=" + b);
break;
}
}
check(allOk, "2833 随机对拍");
System.out.println("2833 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n),空间复杂度 O(1)。
2834. Find the Minimum Possible Sum of a Beautiful Array(找出美丽数组的最小和)
题目描述
给你两个正整数:n 和 target 。
如果数组 nums 满足下述条件,则称其为美丽数组:
nums.length == nnums由两两互不相同的正整数组成- 在范围 0, `n`-1 内,不存在两个不同下标
i和j,使得nums[i]+nums[j]==target
返回符合条件的美丽数组所可能具备的最小和,并对结果取模 10^9 + 7。
示例 1
- 输入 :
n= 2,target= 3 - 输出:4
- 解释 :
nums= 1,3 是美丽数组,其最小和为 4。
示例 2
- 输入 :
n= 3,target= 3 - 输出:8
- 解释 :
nums= 1,3,4 是美丽数组,其最小和为 8。
提示: 1 <= n <= 10^91 <= target <= 10^9
解题思路
解法一:分段求和公式
为让和最小,应尽量选小正数。但若选入 x,则 target - x 不能再选(除非 x == target - x,
即 2x == target,此时无冲突)。
因此把 1...target-1 分成互斥的配对 (x, target-x)。为和最小,每对只能选较小的 x(即 <= target/2)。
策略:
- 先取 1...min(n, target/2) 这些最小合法数,共 c1 = min(n, target/2) 个,和为 1+2+...+c1;
- 若还差 n - c1 个,改从 target、target+1、... 取(这些数彼此两两之和 > target,且与前面所选也不冲突),
共取 n - c1 个数,和为 target + (target+1) + ... + (target + (n-c1) - 1)。
用等差数列求和公式 + 取模。
时间复杂度 O(1),空间复杂度 O(1)。
解法二:贪心逐个构造 + 哈希
从小到大尝试正整数,冲突则跳过直到取满 n 个(小规模对拍用)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
private static final long MOD = 1_000_000_007L;
// ---------- 解法一:分段求和公式(主提交版本) ----------
public int minimumPossibleSum(int n, int target) {
int c1 = Math.min(n, target / 2);
// 1..c1 的和
long sum1 = sum1to(c1);
long sum2 = 0;
int rest = n - c1;
if (rest > 0) {
// target, target+1, ..., target+rest-1
sum2 = (sum1to(target + rest - 1) - sum1to(target - 1) + MOD) % MOD;
}
return (int) ((sum1 + sum2) % MOD);
}
private long sum1to(long x) {
if (x <= 0) return 0;
return (x * (x + 1) / 2) % MOD;
}
// ---------- 解法二:贪心构造(对照) ----------
public int minimumPossibleSumBrute(int n, int target) {
boolean[] used = new boolean[target + n + 5];
long sum = 0;
int cnt = 0;
for (int x = 1; cnt < n; x++) {
if (target - x >= 1 && target - x < used.length && used[target - x]) continue;
used[x] = true;
sum = (sum + x) % MOD;
cnt++;
}
return (int) sum;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.minimumPossibleSum(2, 3) == 4, "2834 例1");
check(s.minimumPossibleSum(3, 3) == 8, "2834 例2");
java.util.Random rnd = new java.util.Random(52);
boolean allOk = true;
for (int t = 0; t < 300; t++) {
int n = 1 + rnd.nextInt(20);
int target = 1 + rnd.nextInt(30);
int a = s.minimumPossibleSum(n, target);
int b = s.minimumPossibleSumBrute(n, target);
if (a != b) {
allOk = false;
System.out.println("对拍失败 n=" + n + " target=" + target + " 公式=" + a + " 贪心=" + b);
break;
}
}
check(allOk, "2834 随机对拍");
// 大数自测
int big = s.minimumPossibleSum(1_000_000_000, 1_000_000_000);
System.out.println("n=1e9,target=1e9 -> " + big);
System.out.println("2834 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(1),空间复杂度 O(1)。
2835. Minimum Operations to Form Subsequence With Target Sum(使子序列的和等于目标的最少操作次数)
题目描述
给你一个下标从 0 开始的数组 nums ,它包含非负整数,且全部为 2 的幂,同时给你一个整数 target 。
一次操作中,你必须对数组做以下修改:
- 选择数组中一个元素
nums[i],满足nums[i]> 1 。 - 将
nums[i]从数组中删除。 - 在
nums的末尾添加两个数,值都为nums[i]/ 2 。
你的目标是让nums的一个子序列的元素和等于target,请你返回达成这一目标的最少操作次数。
如果无法得到这样的子序列,请你返回 -1 。
示例 1
- 输入 :
nums= 1,2,8,target= 7 - 输出:1
- 解释:选择元素 8 拆成 4,4,数组变为 1,2,4,4,子序列 1,2,4 和为 7。
示例 2
- 输入 :
nums= 1,32,1,2,target= 12 - 输出:2
- 解释 :选择 32 拆成 16,16,再选择 16 拆成 8,8,数组变为 1,16,16,8,8,1,2,
子序列 1,8,1,2 和为 12。
示例 3
- 输入 :
nums= 1,2,8,target= 4 - 输出 :1
提示: 1 <= nums.length <= 10001 <= nums[i] <= 10^9nums[i]是 2 的幂1 <= target <= 10^9
解题思路
解法一:按位贪心拆分
把 target 看成二进制。统计 cntt = 值为 2^t 的元素个数(t=0...30)。
从低位到高位检查 target 的第 t 位:
- 若该位为 1:优先用现有 2^t 拼(1 个即可,不足用更高位拆分补足);
更精确的做法:逐位统计"可用量",当某一位需要但不足时,从更高一位拆分一次(操作次数 +1,可用量翻倍)。
具体:维护 cur 表示当前 2^t 的可用个数(含拆分产生的)。对每个 t:
need = (target >> t) & 1
cur += cntt(本身拥有的)
如果 need == 1 且 cur == 0,则需要从更高位拆分:找到最近的高位 p>t 使得 cntp>0 或拆分链存在,
一路拆分下来操作次数累加。
若 need == 1 且 cur > 0:cur--。
每向上一级传递未使用的低半量:cur /= 2 提供给下一轮(可复用拆分余量)。
当所有位处理完且仍有未用元素时,总 sum < target 返回 -1。
时间复杂度 O(31),空间复杂度 O(31)。
解法二:BFS/递归拆分子集搜索
对很小规模枚举所有拆分结果(限制次数),检查子序列和能否等于 target。仅用于对拍。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:按位贪心(主提交版本) ----------
public int minOperations(List<Integer> nums, int target) {
long[] cnt = new long[32];
long total = 0;
for (int v : nums) {
int t = 0;
while ((1L << t) < v) t++;
cnt[t]++;
total += v;
}
if (total < target) return -1;
int ops = 0;
long cur = 0;
for (int t = 0; t < 31; t++) {
cur += cnt[t]; // 当前位上可用的 2^t 个数
int need = (target >> t) & 1;
if (need == 1) {
if (cur > 0) {
cur--;
} else {
// 向上借位拆分
int p = t + 1;
while (p < 31 && cnt[p] == 0) p++;
if (p == 31) return -1;
// 把 2^p 逐级拆到 t
for (int q = p; q > t; q--) {
cnt[q]--;
cnt[q - 1] += 2;
ops++;
}
cur += 1; // 拆分产生的 2^t
cur--;
}
}
// 未使用的 2 个 2^t 合并成 1 个 2^(t+1) 供下一轮
cur /= 2;
}
return ops;
}
// ---------- 解法二:暴力拆分搜索(小规模对照) ----------
public int minOperationsBrute(List<Integer> nums, int target) {
// 检查原始数组
if (canSum(nums, target)) return 0;
// BFS:状态 = 排序后的数组(List),操作数
java.util.Deque<int[]> q = new java.util.ArrayDeque<>();
java.util.Set<String> seen = new java.util.HashSet<>();
// 初始化:把 nums 统计成计数数组
long[] init = new long[32];
for (int v : nums) {
int t = 0;
while ((1L << t) < v) t++;
init[t]++;
}
String key0 = key(init);
seen.add(key0);
q.add(toArr(init));
int ops = 0;
while (!q.isEmpty()) {
int sz = q.size();
for (int s = 0; s < sz; s++) {
long[] st = fromArr(q.poll());
if (canSumFrom(st, target)) return ops;
// 尝试拆分任意 >1 的元素
for (int t = 31; t >= 1; t--) {
if (st[t] > 0) {
long[] ns = st.clone();
ns[t]--;
ns[t - 1] += 2;
String k = key(ns);
if (!seen.contains(k)) {
seen.add(k);
q.add(toArr(ns));
}
}
}
}
ops++;
if (ops > 60) break; // 防止过度搜索
}
return -1;
}
private boolean canSum(List<Integer> nums, int target) {
long[] st = new long[32];
for (int v : nums) {
int t = 0;
while ((1L << t) < v) t++;
st[t]++;
}
return canSumFrom(st, target);
}
// 判断用 st 表示的 2 的幂集合能否组成 target(不允许拆分/借位,纯子集选择)
private boolean canSumFrom(long[] st, int target) {
long[] avail = st.clone();
for (int t = 0; t < 31; t++) {
long need = (target >> t) & 1;
if (need == 1) {
if (avail[t] == 0) return false; // 低位无 2^t 可配,且不能借高位(借=需要操作)
avail[t]--;
}
// 剩余的低位 2 个合成 1 个高位,供更高位使用(子集里多个 2^t 之和等价于 2^(t+1))
avail[t + 1] += avail[t] / 2;
}
return true;
}
private String key(long[] a) {
StringBuilder sb = new StringBuilder();
for (long v : a) sb.append(v).append(',');
return sb.toString();
}
private int[] toArr(long[] a) {
int[] r = new int[32];
for (int i = 0; i < 32; i++) r[i] = (int) a[i];
return r;
}
private long[] fromArr(int[] a) {
long[] r = new long[32];
for (int i = 0; i < 32; i++) r[i] = a[i];
return r;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.minOperations(Arrays.asList(1, 2, 8), 7) == 1, "2835 例1");
check(s.minOperations(Arrays.asList(1, 32, 1, 2), 12) == 2, "2835 例2");
check(s.minOperations(Arrays.asList(1, 2, 8), 4) == 1, "2835 例3");
java.util.Random rnd = new java.util.Random(53);
boolean allOk = true;
for (int t = 0; t < 200; t++) {
int n = 1 + rnd.nextInt(6);
List<Integer> a = new ArrayList<>();
for (int i = 0; i < n; i++) a.add(1 << rnd.nextInt(6));
int target = 1 + rnd.nextInt(64);
int m1 = s.minOperations(a, target);
int m2 = s.minOperationsBrute(a, target);
if (m1 != m2) {
allOk = false;
System.out.println("对拍失败 a=" + a + " target=" + target + " 贪心=" + m1 + " BFS=" + m2);
break;
}
}
check(allOk, "2835 随机对拍");
System.out.println("2835 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(31),空间复杂度 O(31)。
2836. Maximize Value of Function in a Ball Passing Game(在传球游戏中最大化函数值)
题目描述
给你一个长度为 n 下标从 0 开始的整数数组 receiver 和一个整数 k 。
总共有 n 名玩家,玩家编号互不相同,且为 0, `n` - 1 中的整数。这些玩家玩一个传球游戏,
receiver[i] 表示编号为 i 的玩家会传球给编号为 receiver[i] 的玩家。玩家可以传球给自己。
你需要从 n 名玩家中选择一名玩家作为游戏开始时唯一手中有球的玩家,球会被传恰好 k 次。
如果选择编号为 x 的玩家作为开始玩家,定义函数 f(x) 表示从编号为 x 的玩家开始,
k 次传球内所有接触过球玩家的编号之和,如果有玩家多次触球,则累加多次。
换句话说, f(x) = x + receiver[x] + receiver`receiver[x]` + ... + receiverk(共 k+1 项)。
请选择开始玩家 x ,使 f(x) 最大,返回最大值。
示例 1
- 输入 :
receiver= 2,0,1,k= 4 - 输出:6
- 解释 :从
x= 2 开始:2 -> 1 -> 0 -> 2 -> 1,f(2) = 2+1+0+2+1 = 6。
示例 2
- 输入 :
receiver= 1,1,1,2,3,k= 3 - 输出:10
- 解释 :从
x= 4 开始:4 -> 3 -> 2 -> 1,f(4) = 4+3+2+1 = 10。
提示: 1 <= receiver.length == n <= 10^50 <= receiver[i] <= n - 11 <= k <= 10^10
解题思路
解法一:倍增(Binary Lifting)
预处理两个表:
- nxtji:从 i 出发走 2^j 步到达的玩家编号;
- sumji:从 i 出发走 2^j 步过程中触球玩家编号之和(含起点 i,不含到达后尚未继续的终点,按 2^j 层定义)。
递推:nxt0i = receiveri,sum0i = i;
nxtji = nxtj-1nxt\[j-1i],
sumji = sumj-1i + sumj-1nxt\[j-1i]。
查询 f(x):把 k 拆成二进制位从低到高,从 x 开始累加对应块的 sum,并更新 x = nxtjx。
对每个 x 求 f(x) 取最大。
f(x) 是 k+1 个节点之和(含起点),sum 定义已含起点,拼接时注意不重复计算。
实现细节:sumji 定义为「从 i 出发走 2^j 步覆盖的 2^j+1 个节点之和」,
拼接 a -> b(各走 2^j 步)时,总和 = sumA + sumB - b(b 被算两次),
其中 b = 由 a 走完前半段后所在的节点。更稳妥的做法:累计时先加起点再走。
具体:ans=0, cur=x;对每个 set 的位 j:ans += sumjcur;然后若还有更高位,cur = nxtjcur。
注意最后一步 cur 更新放在循环末尾,避免把 k+1 项外的节点计入(sum 含起点,最后一项不重复)。
时间复杂度 O(n log k),空间复杂度 O(n log k)。
解法二:直接模拟走 k 步
对每个 x 循环 k+1 次累加(小 k、小 n 时用),用于对拍。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
// ---------- 解法一:倍增(主提交版本) ----------
public long getMaxFunctionValue(List<Integer> receiver, long k) {
int n = receiver.size();
int bits = 0;
long kk = k;
while (kk > 0) { bits++; kk >>= 1; }
bits = Math.max(bits, 1);
int[][] nxt = new int[bits][n];
long[][] sum = new long[bits][n];
for (int i = 0; i < n; i++) {
nxt[0][i] = receiver.get(i);
sum[0][i] = i; // 含起点
}
for (int j = 1; j < bits; j++) {
for (int i = 0; i < n; i++) {
int mid = nxt[j - 1][i];
nxt[j][i] = nxt[j - 1][mid];
sum[j][i] = sum[j - 1][i] + sum[j - 1][mid];
}
}
// 查询 f(x):走恰好 k 步,即 k+1 个节点。
// sum[j][i] 语义 = 从 i 出发走 2^j 步经过的前 2^j 个节点之和(不含终点 nxt[j][i])。
// 把 k 拆二进制:某位 j 为 1,累加 sum[j][cur] 并把 cur 跳转 nxt[j][cur](只在此刻跳转)。
// 循环结束后 cur = 走完 k 步所在的最终节点,需要补加(nxt 链上的第 k+1 个节点)。
long best = 0;
for (int x = 0; x < n; x++) {
long tot = 0;
int cur = x;
for (int j = 0; j < bits; j++) {
if (((k >> j) & 1L) != 0) {
tot += sum[j][cur];
cur = nxt[j][cur];
}
}
tot += cur; // 最终节点
best = Math.max(best, tot);
}
return best;
}
// ---------- 解法二:直接模拟(对照) ----------
public long getMaxFunctionValueBrute(List<Integer> receiver, long k) {
int n = receiver.size();
long best = Long.MIN_VALUE;
for (int x = 0; x < n; x++) {
long tot = 0;
int cur = x;
for (long step = 0; step <= k; step++) {
tot += cur;
cur = receiver.get(cur);
}
best = Math.max(best, tot);
}
return best;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.getMaxFunctionValue(Arrays.asList(2, 0, 1), 4) == 6, "2836 例1");
check(s.getMaxFunctionValue(Arrays.asList(1, 1, 1, 2, 3), 3) == 10, "2836 例2");
java.util.Random rnd = new java.util.Random(54);
boolean allOk = true;
for (int t = 0; t < 200; t++) {
int n = 2 + rnd.nextInt(8);
List<Integer> a = new ArrayList<>();
for (int i = 0; i < n; i++) a.add(rnd.nextInt(n));
long k = rnd.nextInt(20);
long m1 = s.getMaxFunctionValue(a, k);
long m2 = s.getMaxFunctionValueBrute(a, k);
if (m1 != m2) {
allOk = false;
System.out.println("对拍失败 a=" + a + " k=" + k + " 倍增=" + m1 + " 模拟=" + m2);
break;
}
}
check(allOk, "2836 随机对拍");
// 大 k 自测
long big = s.getMaxFunctionValue(Arrays.asList(2, 0, 1), 10_000_000_000L);
System.out.println("k=1e10 -> " + big);
System.out.println("2836 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n log k),空间复杂度 O(n log k)。
2837. Total Traveled Distance(总旅行距离)
题目类型:数据库(SQL)
题目描述
表:Users
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| user_id | int |
| name | varchar |
+-------------+---------+
user_id 是此表中具有唯一值的列。
此表的每一行包含用户 id 和姓名。
表:Rides
+--------------+------+
| Column Name | Type |
+--------------+------+
| ride_id | int |
| user_id | int |
| distance | int |
+--------------+------+
ride_id 是此表中具有唯一值的列。
此表中的每一行包含乘车 id、用户 id 和旅行距离。
编写一个解决方案,计算每个用户的旅行距离 distance 。如果有用户没有任何旅行记录,那么他们的 distance 应被视为 0 。输出 user_id, name 和总旅行距离 traveled distance 。
按 升序排序 的 user_id 返回结果表。
结果格式如下示例。
示例 1:**
输入:
Users table:
+---------+---------+
| user_id | name |
+---------+---------+
| 17 | Addison |
| 14 | Ethan |
| 4 | Michael |
| 2 | Avery |
| 10 | Eleanor |
+---------+---------+
Rides table:
+---------+---------+----------+
| ride_id | user_id | distance |
+---------+---------+----------+
| 72 | 17 | 160 |
| 42 | 14 | 161 |
| 45 | 4 | 59 |
| 32 | 2 | 197 |
| 15 | 4 | 357 |
| 56 | 2 | 196 |
| 10 | 14 | 25 |
+---------+---------+----------+
输出:
+---------+---------+-------------------+
| user_id | name | traveled distance |
+---------+---------+-------------------+
| 2 | Avery | 393 |
| 4 | Michael | 416 |
| 10 | Eleanor | 0 |
| 14 | Ethan | 186 |
| 17 | Addison | 160 |
+---------+---------+-------------------+
解释:
- User id 为 2 的用户完成了两次旅行,分别为 197 和 196,总旅行距离为 393。
- User id 为 4 的用户完成了两次旅行,分别为 59 和 357,总旅行距离为 416。
- User id 为 14 的用户完成了两次旅行,分别为 161 和 25,总旅行距离为 186。
- User id 为 16 的用户只完成了一次旅行,距离为 160。
- User id 为 10 的用户没有完成任何旅行,因此总旅行距离为 0。
按升序排序的 `user_id` 返回结果表
解题思路
解法一:LEFT JOIN + 聚合(主提交版本)
Users为左表LEFT JOIN Rides,保证没有乘车记录的用户也保留;- 按
user_id分组SUM(distance),无记录者为 NULL,用IFNULL(..., 0)补 0; - 结果按
user_id升序。
代码实现
sql
SELECT user_id, name, IFNULL(SUM(distance), 0) AS 'traveled distance'
FROM
Users
LEFT JOIN Rides USING (user_id)
GROUP BY 1
ORDER BY 1;
复杂度分析
- 时间复杂度 :
O(n),n为表行数,单次扫描聚合(或窗口/连接)。 - 空间复杂度 :
O(n),分组/排序/窗口需要存储结果集。
2838. Maximum Coins Heroes Can Collect(英雄可以获得的最大金币数)
题目描述
在一场战斗中,n 位英雄正在试图击败 m 个怪物。你将得到两个下标从 1 开始的正整数数组
heroes 和 monsters,长度分别为 n 和 m。数组 heroes[i] 代表第 i 位英雄的力量,
而 monsters[i] 代表第 i 个怪物的力量。如果 monsters[j] <= heroes[i],则第 i 位英雄
可以击败第 j 个怪物。你还将获得下标从 1 开始的正整数数组 coins,长度为 m;
coins[i] 表示每位英雄在击败第 i 个怪物后可以获得的金币数。
返回一个长度为 n 的数组 ans,其中 ans[i] 是第 i 位英雄能收集到的最大金币数。
注意:击败怪物后英雄生命值不会减少;多位英雄可以击败同一个怪物,但每个怪物只能被
同一位英雄击败一次。
示例 1
- 输入 :
heroes= 1,4,2,monsters= 1,1,5,2,3,coins= 2,3,4,5,6 - 输出:5,16,10
示例 2
- 输入 :
heroes= 5,monsters= 2,3,1,2,coins= 10,6,5,2 - 输出:23
示例 3
- 输入 :
heroes= 4,4,monsters= 5,7,8,coins= 1,1,1 - 输出 :0,0
提示: 1 <= n == heroes.length <= 10^51 <= m == monsters.length <= 10^5coins.length == m1 <= heroes[i], monsters[i], coins[i] <= 10^9
解题思路
解法一:排序 + 前缀和 + 二分
将怪物按力量升序排序(同时携带对应金币),计算金币的前缀和。
对每位英雄,二分查找最大可击败怪物下标 idx(monstersidx <= heroesi),
答案即前缀和 prefidx + 1。
时间复杂度 O(m log m + n log m),空间复杂度 O(m)。
解法二:暴力逐个英雄扫描
对每个英雄扫描全部怪物累加(小规模对拍)。
代码实现
java
import java.util.Arrays;
public class Solution {
// ---------- 解法一:排序 + 前缀和 + 二分(主提交版本) ----------
public long[] maximumCoins(int[] heroes, int[] monsters, int[] coins) {
int m = monsters.length;
int[][] pairs = new int[m][2];
for (int i = 0; i < m; i++) {
pairs[i][0] = monsters[i];
pairs[i][1] = coins[i];
}
Arrays.sort(pairs, (a, b) -> Integer.compare(a[0], b[0]));
long[] pref = new long[m + 1];
for (int i = 0; i < m; i++) pref[i + 1] = pref[i] + pairs[i][1];
int n = heroes.length;
long[] ans = new long[n];
for (int i = 0; i < n; i++) {
int lo = 0, hi = m - 1, idx = -1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
if (pairs[mid][0] <= heroes[i]) {
idx = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
ans[i] = idx >= 0 ? pref[idx + 1] : 0L;
}
return ans;
}
// ---------- 解法二:暴力(对照) ----------
public long[] maximumCoinsBrute(int[] heroes, int[] monsters, int[] coins) {
int n = heroes.length;
long[] ans = new long[n];
for (int i = 0; i < n; i++) {
long sum = 0;
for (int j = 0; j < monsters.length; j++) {
if (monsters[j] <= heroes[i]) sum += coins[j];
}
ans[i] = sum;
}
return ans;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(Arrays.equals(s.maximumCoins(new int[]{1, 4, 2}, new int[]{1, 1, 5, 2, 3}, new int[]{2, 3, 4, 5, 6}), new long[]{5, 16, 10}), "2838 例1");
check(Arrays.equals(s.maximumCoins(new int[]{5}, new int[]{2, 3, 1, 2}, new int[]{10, 6, 5, 2}), new long[]{23}), "2838 例2");
check(Arrays.equals(s.maximumCoins(new int[]{4, 4}, new int[]{5, 7, 8}, new int[]{1, 1, 1}), new long[]{0, 0}), "2838 例3");
java.util.Random rnd = new java.util.Random(58);
boolean allOk = true;
for (int t = 0; t < 200; t++) {
int n = 1 + rnd.nextInt(8);
int m = 1 + rnd.nextInt(10);
int[] h = new int[n], mo = new int[m], c = new int[m];
for (int i = 0; i < n; i++) h[i] = 1 + rnd.nextInt(12);
for (int i = 0; i < m; i++) { mo[i] = 1 + rnd.nextInt(12); c[i] = 1 + rnd.nextInt(20); }
long[] a = s.maximumCoins(h, mo, c);
long[] b = s.maximumCoinsBrute(h, mo, c);
if (!Arrays.equals(a, b)) {
allOk = false;
System.out.println("对拍失败 heroes=" + Arrays.toString(h) + " monsters=" + Arrays.toString(mo) + " coins=" + Arrays.toString(c));
break;
}
}
check(allOk, "2838 随机对拍");
System.out.println("2838 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(m log m + n log m),空间复杂度 O(m)。
2839. Check if Strings Can be Made Equal With Operations I(判断通过操作能否让字符串相等 I)
题目描述
给你两个字符串 s1 和 s2,长度都为 4,且只包含小写英文字母。
你可以对两个字符串中的任意一个执行以下操作任意次:
- 选择两个下标
i和j且满足j-i= 2,然后交换这个字符串中两个下标对应的字符。
如果你可以让字符串s1和s2相等,那么返回true,否则返回false。
示例 1
- 输入 :
s1= "abcd",s2= "cdab" - 输出 :
true
示例 2
- 输入 :
s1= "abcd",s2= "dacb" - 输出 :
false
提示: s1.length == s2.length == 4s1和s2只包含小写英文字母。
解题思路
解法一:奇偶位分组判断
操作只允许交换距离为 2 的下标,即位置 0 与 2 可互换、位置 1 与 3 可互换。
因此 s1 的 {0,2} 位字符多集必须与 s2 的 {0,2} 位字符多集相同,
{1,3} 位同理。排序后比较即可。
时间复杂度 O(1),空间复杂度 O(1)。
解法二:BFS 全排列状态
从 s1 出发,不断交换满足条件的位(距离 2),用集合去重,看能否得到 s2。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Solution {
// ---------- 解法一:奇偶位分组(主提交版本) ----------
public boolean canBeEqual(String s1, String s2) {
char[] a = new char[]{s1.charAt(0), s1.charAt(2)};
char[] b = new char[]{s2.charAt(0), s2.charAt(2)};
Arrays.sort(a);
Arrays.sort(b);
if (a[0] != b[0] || a[1] != b[1]) return false;
a = new char[]{s1.charAt(1), s1.charAt(3)};
b = new char[]{s2.charAt(1), s2.charAt(3)};
Arrays.sort(a);
Arrays.sort(b);
return a[0] == b[0] && a[1] == b[1];
}
// ---------- 解法二:BFS(对照) ----------
public boolean canBeEqualBrute(String s1, String s2) {
Set<String> seen = new HashSet<>();
Deque<String> q = new ArrayDeque<>();
seen.add(s1);
q.add(s1);
while (!q.isEmpty()) {
String cur = q.poll();
if (cur.equals(s2)) return true;
char[] cs = cur.toCharArray();
for (int i = 0; i + 2 < 4; i++) {
char[] nxt = cs.clone();
char tmp = nxt[i];
nxt[i] = nxt[i + 2];
nxt[i + 2] = tmp;
String ns = new String(nxt);
if (seen.add(ns)) q.add(ns);
}
}
return false;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.canBeEqual("abcd", "cdab"), "2839 例1");
check(!s.canBeEqual("abcd", "dacb"), "2839 例2");
java.util.Random rnd = new java.util.Random(59);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) sb.append((char) ('a' + rnd.nextInt(4)));
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < 4; i++) sb2.append((char) ('a' + rnd.nextInt(4)));
String x = sb.toString(), y = sb2.toString();
boolean a = s.canBeEqual(x, y);
boolean b = s.canBeEqualBrute(x, y);
if (a != b) {
allOk = false;
System.out.println("对拍失败 s1=" + x + " s2=" + y + " 主解=" + a + " BFS=" + b);
break;
}
}
check(allOk, "2839 随机对拍");
System.out.println("2839 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(1),空间复杂度 O(1)。
2840. Check if Strings Can be Made Equal With Operations II(判断通过操作能否让字符串相等 II)
题目描述
给你两个字符串 s1 和 s2,长度都为 n,且只包含小写英文字母。
你可以对两个字符串中的任意一个执行以下操作任意次:
- 选择两个下标
i和j,满足i<j且j-i是偶数,然后交换这个字符串中两个下标对应的字符。
如果你可以让字符串s1和s2相等,那么返回true,否则返回false。
示例 1
- 输入 :
s1= "abcdba",s2= "cabdab" - 输出 :
true
示例 2
- 输入 :
s1= "abe",s2= "bea" - 输出 :
false
提示: n == s1.length == s2.length1 <= n <= 10^5s1和s2只包含小写英文字母。
解题思路
解法一:奇偶下标分组
j - i 为偶数意味着两个下标奇偶性相同,因此同奇偶的位置之间可以任意交换字符。
等价于:s1 偶数下标位置字符的多重集 == s2 偶数下标位置字符的多重集,且奇数下标位置同理。
用两个 26 长计数数组分别统计奇偶位,比较即可。
时间复杂度 O(n),空间复杂度 O(1)。
解法二:BFS/DFS 状态搜索
小规模时枚举所有可交换状态(对照)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
public class Solution {
// ---------- 解法一:奇偶计数(主提交版本) ----------
public boolean checkStrings(String s1, String s2) {
int[] even = new int[26], odd = new int[26];
for (int i = 0; i < s1.length(); i++) {
if ((i & 1) == 0) even[s1.charAt(i) - 'a']++;
else odd[s1.charAt(i) - 'a']++;
}
for (int i = 0; i < s2.length(); i++) {
if ((i & 1) == 0) even[s2.charAt(i) - 'a']--;
else odd[s2.charAt(i) - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (even[i] != 0 || odd[i] != 0) return false;
}
return true;
}
// ---------- 解法二:BFS(对照,小长度) ----------
public boolean checkStringsBrute(String s1, String s2) {
if (s1.length() > 8) return checkStrings(s1, s2); // 状态爆炸保护
Set<String> seen = new HashSet<>();
Deque<String> q = new ArrayDeque<>();
seen.add(s1);
q.add(s1);
while (!q.isEmpty()) {
String cur = q.poll();
if (cur.equals(s2)) return true;
char[] cs = cur.toCharArray();
int n = cs.length;
for (int i = 0; i < n; i++) {
for (int j = i + 2; j < n; j += 2) {
char[] nxt = cs.clone();
char tmp = nxt[i];
nxt[i] = nxt[j];
nxt[j] = tmp;
String ns = new String(nxt);
if (seen.add(ns)) q.add(ns);
}
}
}
return false;
}
// ---------- 本地自测 ----------
public static void main(String[] args) {
Solution s = new Solution();
check(s.checkStrings("abcdba", "cabdab"), "2840 例1");
check(!s.checkStrings("abe", "bea"), "2840 例2");
java.util.Random rnd = new java.util.Random(60);
boolean allOk = true;
for (int t = 0; t < 500; t++) {
int n = 1 + rnd.nextInt(6);
StringBuilder sb = new StringBuilder(), sb2 = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append((char) ('a' + rnd.nextInt(4)));
sb2.append((char) ('a' + rnd.nextInt(4)));
}
String x = sb.toString(), y = sb2.toString();
boolean a = s.checkStrings(x, y);
boolean b = s.checkStringsBrute(x, y);
if (a != b) {
allOk = false;
System.out.println("对拍失败 s1=" + x + " s2=" + y + " 主解=" + a + " BFS=" + b);
break;
}
}
check(allOk, "2840 随机对拍");
System.out.println("2840 all tests done");
}
static void check(boolean ok, String name) {
System.out.println((ok ? "PASS " : "FAIL ") + name);
}
}
复杂度分析
时间复杂度 O(n),空间复杂度 O(1)。